@gobing-ai/knowledge-kit 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +120 -26
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +22187 -0
- package/plugins/generations/content-gen/plugin.json +1 -1
- package/plugins/generations/core-facts-gen/dist/index.js +22068 -0
- package/plugins/generations/core-facts-gen/plugin.json +1 -1
- package/plugins/generations/daily-article-gen/dist/index.js +22050 -0
- package/plugins/generations/daily-article-gen/plugin.json +1 -1
- package/plugins/generations/daily-article-gen/src/index.ts +13 -1
- package/plugins/generations/dailynews-gen/dist/index.js +22344 -0
- package/plugins/generations/dailynews-gen/plugin.json +1 -1
- package/plugins/generations/episode-plan-gen/dist/index.js +22503 -0
- package/plugins/generations/episode-plan-gen/plugin.json +1 -1
- package/plugins/generations/episode-plan-gen/src/index.ts +11 -0
- package/plugins/generations/image-gen/config.example.yaml +75 -0
- package/plugins/generations/image-gen/dist/index.js +24862 -0
- package/plugins/generations/image-gen/package.json +17 -0
- package/plugins/generations/image-gen/plugin.json +7 -0
- package/plugins/generations/image-gen/presets/formats/cover.yaml +57 -0
- package/plugins/generations/image-gen/presets/formats/free.yaml +46 -0
- package/plugins/generations/image-gen/presets/formats/illustration.yaml +48 -0
- package/plugins/generations/image-gen/presets/styles/clean-webapp-ui.yaml +28 -0
- package/plugins/generations/image-gen/presets/styles/cute.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/editorial.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/fresh.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/minimalist.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/photorealistic.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/sketch.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/technical-diagram.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/vibrant.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/warm.yaml +3 -0
- package/plugins/generations/image-gen/src/bytes.ts +19 -0
- package/plugins/generations/image-gen/src/index.ts +319 -0
- package/plugins/generations/image-gen/src/job.ts +143 -0
- package/plugins/generations/image-gen/src/paths.ts +31 -0
- package/plugins/generations/image-gen/src/presets.ts +344 -0
- package/plugins/generations/image-gen/src/providers/agnes.ts +110 -0
- package/plugins/generations/image-gen/src/providers/azure.ts +153 -0
- package/plugins/generations/image-gen/src/providers/codex-cli.ts +170 -0
- package/plugins/generations/image-gen/src/providers/dashscope.ts +485 -0
- package/plugins/generations/image-gen/src/providers/google.ts +268 -0
- package/plugins/generations/image-gen/src/providers/huggingface.ts +59 -0
- package/plugins/generations/image-gen/src/providers/jimeng.ts +259 -0
- package/plugins/generations/image-gen/src/providers/minimax.ts +171 -0
- package/plugins/generations/image-gen/src/providers/openai.ts +319 -0
- package/plugins/generations/image-gen/src/providers/openrouter.ts +257 -0
- package/plugins/generations/image-gen/src/providers/refs.ts +24 -0
- package/plugins/generations/image-gen/src/providers/replicate.ts +279 -0
- package/plugins/generations/image-gen/src/providers/seedream.ts +128 -0
- package/plugins/generations/image-gen/src/providers/types.ts +286 -0
- package/plugins/generations/image-gen/src/providers/zai.ts +237 -0
- package/plugins/generations/image-gen/tsconfig.json +8 -0
- package/plugins/generations/news-report-gen/dist/index.js +22193 -0
- package/plugins/generations/news-report-gen/package.json +17 -0
- package/plugins/generations/news-report-gen/plugin.json +7 -0
- package/plugins/generations/news-report-gen/src/index.ts +308 -0
- package/plugins/generations/news-report-gen/tsconfig.json +4 -0
- package/plugins/generations/omni-voice-gen/Makefile +14 -0
- package/plugins/generations/omni-voice-gen/README.md +112 -0
- package/plugins/generations/omni-voice-gen/bin/omni-voice-gen +2 -0
- package/plugins/generations/omni-voice-gen/dist/omni-voice-gen-prr8skpb. +2 -0
- package/plugins/generations/omni-voice-gen/dist/omni-voice-gen.js +6 -0
- package/plugins/generations/omni-voice-gen/plugin.json +6 -0
- package/plugins/generations/omni-voice-gen/profiles.json +12 -0
- package/plugins/generations/omni-voice-gen/pyproject.toml +25 -0
- package/plugins/generations/omni-voice-gen/scripts/coverage_gate.py +74 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__init__.py +1 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__main__.py +39 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/audio.py +190 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/backend.py +150 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/contract.py +76 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/mp3.py +60 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +289 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/profiles.py +100 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/qc.py +234 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +352 -0
- package/plugins/generations/omni-voice-gen/uv.lock +3510 -0
- package/plugins/generations/voice-gen/dist/index.js +23055 -0
- package/plugins/generations/voice-gen/plugin.json +1 -1
- package/plugins/generations/voice-gen/src/index.ts +16 -1
- package/plugins/generations/voice-gen/src/voicebox-client.ts +3 -1
- package/plugins/ingestions/aihot-ingest/dist/index.js +22378 -0
- package/plugins/ingestions/aihot-ingest/plugin.json +1 -1
- package/plugins/ingestions/horizon-ingest/dist/index.js +22125 -0
- package/plugins/ingestions/horizon-ingest/plugin.json +1 -1
- package/plugins/ingestions/karakeep-local/dist/index.js +24204 -0
- package/plugins/ingestions/karakeep-local/plugin.json +1 -1
- package/plugins/ingestions/last30days-ingest/dist/index.js +22070 -0
- package/plugins/ingestions/last30days-ingest/plugin.json +1 -1
- package/plugins/ingestions/web-search/dist/index.js +24399 -0
- package/plugins/ingestions/web-search/plugin.json +1 -1
- package/plugins/kk/commands/image-extract.md +40 -0
- package/plugins/kk/commands/image-generate.md +32 -0
- package/plugins/kk/config.example.yaml +80 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/skills/image-authoring/SKILL.md +257 -0
- package/plugins/kk/skills/image-authoring/references/format-drafting.md +57 -0
- package/plugins/kk/skills/image-authoring/references/illustration-positions.md +87 -0
- package/plugins/kk/skills/image-authoring/references/migrating-from-wt.md +31 -0
- package/plugins/kk/skills/image-authoring/references/providers.md +52 -0
- package/plugins/kk/skills/image-authoring/references/style-extraction.md +139 -0
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +130 -30
- package/plugins/publishings/emdash-pub/dist/index.js +22263 -0
- package/plugins/publishings/emdash-pub/plugin.json +1 -1
- package/plugins/publishings/podcast-pub/dist/index.js +22650 -0
- package/plugins/publishings/podcast-pub/plugin.json +8 -2
- package/plugins/publishings/podcast-pub/src/index.ts +18 -2
- package/plugins/publishings/podcast-pub/src/show-notes.ts +56 -9
- package/plugins/publishings/qiita-pub/dist/index.js +22101 -0
- package/plugins/publishings/qiita-pub/plugin.json +1 -1
- package/plugins/publishings/surfdash-pub/dist/index.js +22323 -0
- package/plugins/publishings/surfdash-pub/plugin.json +1 -1
- package/plugins/publishings/surfdash-pub/src/index.ts +109 -9
- package/plugins/publishings/zenn-pub/dist/index.js +22142 -0
- package/plugins/publishings/zenn-pub/plugin.json +1 -1
- package/plugins/sp/scripts/batch-preflight.mjs +346 -0
- package/plugins/sp/scripts/batch-preflight.ts +459 -0
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +615 -0
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +846 -0
- package/plugins/sp/scripts/daily-summary/logger.ts +28 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +223 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +367 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +132 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +169 -0
- package/plugins/sp/scripts/feature-dev-precheck.mjs +171 -0
- package/plugins/sp/scripts/feature-dev-precheck.ts +238 -0
- package/plugins/sp/scripts/feature-sync-bounded.mjs +285 -0
- package/plugins/sp/scripts/feature-sync-bounded.ts +478 -0
- package/plugins/sp/scripts/history-anatomy-cache.mjs +902 -0
- package/plugins/sp/scripts/history-anatomy-cache.ts +1028 -0
- package/plugins/sp/scripts/idea-handoff.mjs +22 -0
- package/plugins/sp/scripts/idea-handoff.ts +44 -0
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +185 -0
- package/plugins/sp/scripts/inline-run-setup.ts +198 -0
- package/plugins/sp/scripts/pr-reviewing.mjs +769 -0
- package/plugins/sp/scripts/pr-reviewing.ts +925 -0
- package/plugins/sp/scripts/quality-gate.mjs +179 -0
- package/plugins/sp/scripts/quality-gate.ts +217 -0
- package/plugins/sp/scripts/script-contract-check.ts +319 -0
- package/plugins/sp/scripts/stage-registry-adapter.ts +1533 -0
- package/plugins/sp/scripts/surface-drift-inventory.ts +929 -0
- package/plugins/sp/scripts/task-evidence-precheck.ts +181 -0
- package/plugins/sp/scripts/task-size-precheck.ts +175 -0
- package/plugins/sp/scripts/transition-shim-check.ts +238 -0
- package/plugins/sp/scripts/validate-commands.ts +689 -0
- package/plugins/sp/scripts/validate-flag-contracts.ts +878 -0
- package/plugins/sp/scripts/verify-answer-lint.ts +530 -0
- package/plugins/sp/scripts/workflow-step-profile.mjs +316 -0
- package/plugins/sp/scripts/workflow-step-profile.ts +456 -0
- package/plugins/sp/scripts/wrapup-steps.mjs +373 -0
- package/plugins/sp/scripts/wrapup-steps.ts +466 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { readBytes } from '../bytes';
|
|
3
|
+
import type { ImageProvider, ImageRequest, ImageResult, ProviderFactory, ProviderOptions } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Seedream adapter (0107 R2) — the first wire-speaking 0103 adapter.
|
|
7
|
+
* Wire prior art: vendors/baoyu-skills/skills/baoyu-image-gen/scripts/providers/seedream.ts
|
|
8
|
+
* (MIT © 2026 Jim Liu). POST `{base}/images/generations`, Bearer ARK_API_KEY.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const DEFAULT_BASE_URL = 'https://ark.cn-beijing.volces.com/api/v3';
|
|
12
|
+
const DEFAULT_MODEL = 'doubao-seedream-5-0-260128';
|
|
13
|
+
const REF_MODEL_FRAGMENTS = ['doubao-seedream-5-0', 'doubao-seedream-4-5-', 'doubao-seedream-4-0-'];
|
|
14
|
+
|
|
15
|
+
interface SeedreamResponse {
|
|
16
|
+
data?: Array<{ url?: string; b64_json?: string; error?: { code?: string; message?: string } }>;
|
|
17
|
+
error?: { code?: string; message?: string };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** ARK `size`: explicit pixels win; the aspect ratio rides verbatim (seedream 4.x accepts '21:9'); else 2K. */
|
|
21
|
+
export function mapSize(req: ImageRequest): string {
|
|
22
|
+
if (req.width !== undefined && req.height !== undefined) {
|
|
23
|
+
return `${req.width}x${req.height}`;
|
|
24
|
+
}
|
|
25
|
+
if (req.aspectRatio !== undefined) {
|
|
26
|
+
return req.aspectRatio;
|
|
27
|
+
}
|
|
28
|
+
return '2K';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function extensionFromUrl(url: string): string {
|
|
32
|
+
// data.b64_json path carries no extension — default png; URL paths end ?sig=… so take the last clean segment.
|
|
33
|
+
const match = /(?:png|jpe?g|webp)/i.exec(url.split('?')[0] ?? '');
|
|
34
|
+
return match === null ? 'jpg' : match[0].toLowerCase().replace(/^jpeg$/, 'jpg');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const createSeedream: ProviderFactory = (options?: ProviderOptions): ImageProvider => {
|
|
38
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
39
|
+
const env = options?.env ?? process.env;
|
|
40
|
+
const readEnv = (key: string): string => env[key] ?? '';
|
|
41
|
+
const supportsRef = (model: string): boolean => REF_MODEL_FRAGMENTS.some((fragment) => model.includes(fragment));
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
name: 'seedream',
|
|
45
|
+
defaultModel: () => DEFAULT_MODEL,
|
|
46
|
+
isAvailable: () => readEnv('ARK_API_KEY') !== '',
|
|
47
|
+
supportsReferenceImages: supportsRef,
|
|
48
|
+
validate(req: ImageRequest): void {
|
|
49
|
+
if (req.referenceImages !== undefined && req.referenceImages.length > 0 && !supportsRef(req.model)) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`seedream model '${req.model}' does not accept reference images; use a doubao-seedream 4.x+ model`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
async generate(req: ImageRequest): Promise<ImageResult> {
|
|
56
|
+
const apiKey = readEnv('ARK_API_KEY');
|
|
57
|
+
if (apiKey === '') {
|
|
58
|
+
throw new Error("Provider 'seedream' requires ARK_API_KEY");
|
|
59
|
+
}
|
|
60
|
+
const base = readEnv('SEEDREAM_BASE_URL') || DEFAULT_BASE_URL;
|
|
61
|
+
const body: Record<string, unknown> = {
|
|
62
|
+
model: req.model,
|
|
63
|
+
prompt: req.prompt,
|
|
64
|
+
size: mapSize(req),
|
|
65
|
+
response_format: 'url',
|
|
66
|
+
watermark: false,
|
|
67
|
+
};
|
|
68
|
+
if (req.referenceImages !== undefined && req.referenceImages.length > 0) {
|
|
69
|
+
// Local paths cannot ride the wire: lift each to a base64 data URI.
|
|
70
|
+
const lifted = await Promise.all(
|
|
71
|
+
req.referenceImages.map(async (path) => {
|
|
72
|
+
const bytes = await readBytes(path);
|
|
73
|
+
return `data:image/png;base64,${Buffer.from(bytes).toString('base64')}`;
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
body.image = lifted.length === 1 ? lifted[0] : lifted;
|
|
77
|
+
body.response_format = 'b64_json';
|
|
78
|
+
}
|
|
79
|
+
const resp = await fetchFn(`${base}/images/generations`, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: { authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' },
|
|
82
|
+
body: JSON.stringify(body),
|
|
83
|
+
});
|
|
84
|
+
const text = await resp.text();
|
|
85
|
+
if (!resp.ok) {
|
|
86
|
+
throw new Error(`seedream HTTP ${resp.status}: ${text.slice(0, 200)}`);
|
|
87
|
+
}
|
|
88
|
+
let parsed: SeedreamResponse;
|
|
89
|
+
try {
|
|
90
|
+
parsed = JSON.parse(text) as SeedreamResponse;
|
|
91
|
+
} catch {
|
|
92
|
+
throw new Error(`seedream returned non-JSON: ${text.slice(0, 120)}`);
|
|
93
|
+
}
|
|
94
|
+
const image = parsed.data?.[0];
|
|
95
|
+
if (parsed.error !== undefined) {
|
|
96
|
+
throw new Error(`seedream error ${parsed.error.code ?? ''}: ${parsed.error.message ?? ''}`);
|
|
97
|
+
}
|
|
98
|
+
if (image === undefined) {
|
|
99
|
+
throw new Error('seedream returned no image data');
|
|
100
|
+
}
|
|
101
|
+
if (image.b64_json !== undefined) {
|
|
102
|
+
return {
|
|
103
|
+
bytes: Uint8Array.from(Buffer.from(image.b64_json, 'base64')),
|
|
104
|
+
extension: 'png',
|
|
105
|
+
provider: 'seedream',
|
|
106
|
+
model: req.model,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (image.url !== undefined) {
|
|
110
|
+
const r = await fetchFn(image.url);
|
|
111
|
+
if (!r.ok) {
|
|
112
|
+
throw new Error(`seedream fetch URL HTTP ${r.status}`);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
bytes: new Uint8Array(await r.arrayBuffer()),
|
|
116
|
+
extension: extensionFromUrl(image.url),
|
|
117
|
+
provider: 'seedream',
|
|
118
|
+
model: req.model,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
throw new Error(
|
|
122
|
+
image.error === undefined
|
|
123
|
+
? 'seedream returned neither url nor b64_json'
|
|
124
|
+
: `seedream image error ${image.error.code ?? ''}: ${image.error.message ?? ''}`,
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
};
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 0103 — the frozen `ImageProvider` contract: 0104 codes against it and the 12
|
|
3
|
+
* baoyu-derived adapters implement it. Wire-speaking adapters are later tickets; this
|
|
4
|
+
* file carries only the request/result types, the registry, and the selection order.
|
|
5
|
+
*
|
|
6
|
+
* Prior art: vendors/baoyu-skills/skills/baoyu-image-gen/scripts/ (MIT © 2026 Jim Liu).
|
|
7
|
+
* The deliberate differences from baoyu's `ProviderModule` (scripts/main.ts:15-20) —
|
|
8
|
+
* `ImageRequest` replaces `CliArgs`, `isAvailable()` and `supportsReferenceImages(model)`
|
|
9
|
+
* are new, `ImageResult` wraps the bytes — are recorded in the task Design.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createAgnes } from './agnes';
|
|
13
|
+
import { createAzure } from './azure';
|
|
14
|
+
import { createCodexCli } from './codex-cli';
|
|
15
|
+
import { createDashscope } from './dashscope';
|
|
16
|
+
import { createGoogle } from './google';
|
|
17
|
+
import { createHuggingface } from './huggingface';
|
|
18
|
+
import { createJimeng } from './jimeng';
|
|
19
|
+
import { createMinimax } from './minimax';
|
|
20
|
+
import { createOpenai } from './openai';
|
|
21
|
+
import { createOpenrouter } from './openrouter';
|
|
22
|
+
import { createReplicate } from './replicate';
|
|
23
|
+
import { createSeedream } from './seedream';
|
|
24
|
+
import { createZai } from './zai';
|
|
25
|
+
|
|
26
|
+
/** The two quality levels baoyu exposes (SKILL.md); every provider maps or ignores them. */
|
|
27
|
+
export type ImageQuality = 'normal' | '2k';
|
|
28
|
+
|
|
29
|
+
/** One generation request; a non-empty `referenceImages` means image-to-image (0105). */
|
|
30
|
+
export interface ImageRequest {
|
|
31
|
+
prompt: string;
|
|
32
|
+
model: string;
|
|
33
|
+
/** e.g. '21:9' — from FormatPreset.aspectRatio (0102). */
|
|
34
|
+
aspectRatio?: string;
|
|
35
|
+
/** Optional exact pixels; providers that only take an aspect ratio ignore them. */
|
|
36
|
+
width?: number;
|
|
37
|
+
height?: number;
|
|
38
|
+
quality?: ImageQuality;
|
|
39
|
+
/** From StylePreset.negative (0102); providers without negative prompts ignore it. */
|
|
40
|
+
negative?: string;
|
|
41
|
+
/** Absolute paths; empty or undefined = text-to-image. */
|
|
42
|
+
referenceImages?: string[];
|
|
43
|
+
/** Default 1. v1 rule: a caller wanting N images issues N calls; adapters return the first. */
|
|
44
|
+
n?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Adapters return bytes, never paths — 0104 owns file naming and writing. */
|
|
48
|
+
export interface ImageResult {
|
|
49
|
+
bytes: Uint8Array;
|
|
50
|
+
/** 'png' | 'jpg' | 'webp' — the provider decides. */
|
|
51
|
+
extension: string;
|
|
52
|
+
provider: string;
|
|
53
|
+
model: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ImageProvider {
|
|
57
|
+
readonly name: string;
|
|
58
|
+
defaultModel(): string;
|
|
59
|
+
/**
|
|
60
|
+
* Env-presence readiness — MUST NOT perform network or subprocess calls. A stale
|
|
61
|
+
* key still passes; it surfaces as the adapter's own auth error at generate()
|
|
62
|
+
* time, matching baoyu's throw-at-call-time behaviour (task Q&A, closed).
|
|
63
|
+
*/
|
|
64
|
+
isAvailable(): boolean;
|
|
65
|
+
/** Queryable ref-image capability; 0105 routes `--ref` work on this fact. */
|
|
66
|
+
supportsReferenceImages(model: string): boolean;
|
|
67
|
+
/** Throw on an impossible combination (e.g. Imagen + referenceImages) before spending money. */
|
|
68
|
+
validate?(req: ImageRequest): void;
|
|
69
|
+
generate(req: ImageRequest): Promise<ImageResult>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Injection seam for tests — mirrors `VoiceboxClientOptions.fetch` (voice-gen). */
|
|
73
|
+
export interface ProviderOptions {
|
|
74
|
+
fetch?: typeof fetch;
|
|
75
|
+
/** Environment consulted instead of process.env (tests). */
|
|
76
|
+
env?: Record<string, string | undefined>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type ProviderFactory = (options?: ProviderOptions) => ImageProvider;
|
|
80
|
+
|
|
81
|
+
/** Env var consulted for the provider name when no explicit request is passed (selection step 2). */
|
|
82
|
+
const IMAGE_GEN_PROVIDER_ENV = 'IMAGE_GEN_PROVIDER';
|
|
83
|
+
|
|
84
|
+
/** Providers that may never be auto-selected, even if listed in AUTO_ORDER later (task 0103 R3). */
|
|
85
|
+
const NEVER_AUTO: ReadonlySet<string> = new Set(['codex-cli']);
|
|
86
|
+
|
|
87
|
+
/** Registry row: the availability facts an adapter must honour, verbatim from its baoyu source. */
|
|
88
|
+
interface ProviderSpec {
|
|
89
|
+
readonly name: string;
|
|
90
|
+
/** Env vars where any one present = usable (google's `A || B` fallback chains). */
|
|
91
|
+
readonly env: readonly string[];
|
|
92
|
+
/** Env vars where every one must be present (jimeng's access/secret key pair). */
|
|
93
|
+
readonly envAll?: readonly string[];
|
|
94
|
+
readonly defaultModel: string;
|
|
95
|
+
/** Model fragments that accept reference images; omit = every model does; empty = none. */
|
|
96
|
+
readonly refModels?: readonly string[];
|
|
97
|
+
/** Replaces the missing-credential text when readiness is not an env var (codex-cli). */
|
|
98
|
+
readonly note?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
type ProviderRegistration = ProviderSpec & { readonly create: ProviderFactory };
|
|
102
|
+
|
|
103
|
+
/** The 12 baoyu adapters, one row each (task 0103 R1 inventory). Keys sorted alphabetically. */
|
|
104
|
+
export const PROVIDERS: Readonly<Record<string, ProviderRegistration>> = {
|
|
105
|
+
agnes: {
|
|
106
|
+
name: 'agnes',
|
|
107
|
+
env: ['AGNES_API_KEY'],
|
|
108
|
+
defaultModel: 'agnes-image-2.1-flash',
|
|
109
|
+
create: createAgnes,
|
|
110
|
+
},
|
|
111
|
+
azure: {
|
|
112
|
+
name: 'azure',
|
|
113
|
+
env: ['AZURE_OPENAI_API_KEY'],
|
|
114
|
+
defaultModel: 'gpt-image-2',
|
|
115
|
+
create: createAzure,
|
|
116
|
+
},
|
|
117
|
+
'codex-cli': {
|
|
118
|
+
name: 'codex-cli',
|
|
119
|
+
env: [],
|
|
120
|
+
defaultModel: 'codex-image-gen',
|
|
121
|
+
note: "requires the 'codex' CLI on PATH with an active login (never auto-selected)",
|
|
122
|
+
create: createCodexCli,
|
|
123
|
+
},
|
|
124
|
+
dashscope: {
|
|
125
|
+
name: 'dashscope',
|
|
126
|
+
env: ['DASHSCOPE_API_KEY'],
|
|
127
|
+
defaultModel: 'qwen-image-2.0-pro',
|
|
128
|
+
refModels: ['wan2.7'],
|
|
129
|
+
create: createDashscope,
|
|
130
|
+
},
|
|
131
|
+
google: {
|
|
132
|
+
name: 'google',
|
|
133
|
+
env: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
|
|
134
|
+
defaultModel: 'gemini-3-pro-image',
|
|
135
|
+
refModels: [
|
|
136
|
+
'gemini-3-pro-image',
|
|
137
|
+
'gemini-3.1-flash-image',
|
|
138
|
+
'gemini-3-pro-image-preview',
|
|
139
|
+
'gemini-3-flash-preview',
|
|
140
|
+
'gemini-3.1-flash-image-preview',
|
|
141
|
+
],
|
|
142
|
+
create: createGoogle,
|
|
143
|
+
},
|
|
144
|
+
huggingface: {
|
|
145
|
+
name: 'huggingface',
|
|
146
|
+
env: ['HUGGINGFACE_API_TOKEN'],
|
|
147
|
+
defaultModel: 'stabilityai/stable-diffusion-xl-base-1.0',
|
|
148
|
+
create: createHuggingface,
|
|
149
|
+
},
|
|
150
|
+
jimeng: {
|
|
151
|
+
name: 'jimeng',
|
|
152
|
+
env: [],
|
|
153
|
+
envAll: ['JIMENG_ACCESS_KEY_ID', 'JIMENG_SECRET_ACCESS_KEY'],
|
|
154
|
+
defaultModel: 'jimeng_t2i_v40',
|
|
155
|
+
create: createJimeng,
|
|
156
|
+
},
|
|
157
|
+
minimax: {
|
|
158
|
+
name: 'minimax',
|
|
159
|
+
env: ['MINIMAX_API_KEY'],
|
|
160
|
+
defaultModel: 'image-01',
|
|
161
|
+
create: createMinimax,
|
|
162
|
+
},
|
|
163
|
+
openai: {
|
|
164
|
+
name: 'openai',
|
|
165
|
+
env: ['OPENAI_API_KEY'],
|
|
166
|
+
defaultModel: 'gpt-image-2',
|
|
167
|
+
refModels: ['gpt-image'],
|
|
168
|
+
create: createOpenai,
|
|
169
|
+
},
|
|
170
|
+
openrouter: {
|
|
171
|
+
name: 'openrouter',
|
|
172
|
+
env: ['OPENROUTER_API_KEY'],
|
|
173
|
+
defaultModel: 'google/gemini-3.1-flash-image',
|
|
174
|
+
create: createOpenrouter,
|
|
175
|
+
},
|
|
176
|
+
replicate: {
|
|
177
|
+
name: 'replicate',
|
|
178
|
+
env: ['REPLICATE_API_TOKEN'],
|
|
179
|
+
defaultModel: 'google/nano-banana-2',
|
|
180
|
+
create: createReplicate,
|
|
181
|
+
},
|
|
182
|
+
seedream: {
|
|
183
|
+
name: 'seedream',
|
|
184
|
+
env: ['ARK_API_KEY'],
|
|
185
|
+
defaultModel: 'doubao-seedream-5-0-260128',
|
|
186
|
+
refModels: ['doubao-seedream-5-0', 'doubao-seedream-4-5-', 'doubao-seedream-4-0-'],
|
|
187
|
+
// 0107: the first wire-speaking adapter — replaces the envOnly shell.
|
|
188
|
+
create: createSeedream,
|
|
189
|
+
},
|
|
190
|
+
zai: {
|
|
191
|
+
name: 'zai',
|
|
192
|
+
env: ['ZAI_API_KEY', 'BIGMODEL_API_KEY'],
|
|
193
|
+
defaultModel: 'glm-image',
|
|
194
|
+
refModels: [],
|
|
195
|
+
create: createZai,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/** Frozen auto-selection priority (task 0103 Design). `codex-cli` is absent on purpose. */
|
|
200
|
+
export const AUTO_ORDER = [
|
|
201
|
+
'huggingface',
|
|
202
|
+
'google',
|
|
203
|
+
'openai',
|
|
204
|
+
'replicate',
|
|
205
|
+
'openrouter',
|
|
206
|
+
'dashscope',
|
|
207
|
+
'seedream',
|
|
208
|
+
'zai',
|
|
209
|
+
'minimax',
|
|
210
|
+
'jimeng',
|
|
211
|
+
'azure',
|
|
212
|
+
'agnes',
|
|
213
|
+
] as const;
|
|
214
|
+
|
|
215
|
+
/** Human-readable readiness requirement for one registry row. */
|
|
216
|
+
function envSummary(reg: ProviderRegistration): string {
|
|
217
|
+
if (reg.note !== undefined) {
|
|
218
|
+
return reg.note;
|
|
219
|
+
}
|
|
220
|
+
// env entries are any-of alternatives (' or '); envAll entries are all-required (' and ');
|
|
221
|
+
// the two groups are both required when present (' and ' across groups).
|
|
222
|
+
const groups: string[] = [];
|
|
223
|
+
if (reg.env.length > 0) groups.push(reg.env.join(' or '));
|
|
224
|
+
if (reg.envAll !== undefined && reg.envAll.length > 0) groups.push(reg.envAll.join(' and '));
|
|
225
|
+
return groups.join(' and ');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function notConfiguredMessage(reg: ProviderRegistration): string {
|
|
229
|
+
if (reg.note !== undefined) {
|
|
230
|
+
return `Provider '${reg.name}' is not available: ${reg.note}`;
|
|
231
|
+
}
|
|
232
|
+
return `Provider '${reg.name}' is not configured: missing ${envSummary(reg)} (set it in plugins.generations.image-gen.env or process.env)`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Resolve which provider name a generation job uses. First match wins:
|
|
237
|
+
* 1. `requested` — an explicit name from the job input; unknown throws listing all 12
|
|
238
|
+
* valid names, known-but-unconfigured throws naming the missing env var (no silent
|
|
239
|
+
* downgrade: that would change what the operator paid for).
|
|
240
|
+
* 2. `IMAGE_GEN_PROVIDER` in the environment, same two failure rules.
|
|
241
|
+
* 3. The first name in AUTO_ORDER whose provider `isAvailable()`.
|
|
242
|
+
* 4. Nothing available → throw listing every provider with what each one needs.
|
|
243
|
+
*/
|
|
244
|
+
export function selectProvider(
|
|
245
|
+
requested?: string,
|
|
246
|
+
options?: {
|
|
247
|
+
/** Environment consulted instead of process.env (tests). */
|
|
248
|
+
env?: Record<string, string | undefined>;
|
|
249
|
+
/** Availability override; default asks the registered provider's isAvailable(). */
|
|
250
|
+
isAvailable?: (name: string) => boolean;
|
|
251
|
+
},
|
|
252
|
+
): string {
|
|
253
|
+
const env = options?.env ?? process.env;
|
|
254
|
+
const available =
|
|
255
|
+
options?.isAvailable ??
|
|
256
|
+
((name: string): boolean => {
|
|
257
|
+
const reg = PROVIDERS[name];
|
|
258
|
+
return reg?.create({ env }).isAvailable() ?? false;
|
|
259
|
+
});
|
|
260
|
+
const names = Object.keys(PROVIDERS).sort();
|
|
261
|
+
|
|
262
|
+
const explicit = requested ?? env[IMAGE_GEN_PROVIDER_ENV];
|
|
263
|
+
if (explicit !== undefined && explicit !== '') {
|
|
264
|
+
const reg = PROVIDERS[explicit];
|
|
265
|
+
if (reg === undefined) {
|
|
266
|
+
throw new Error(`Unknown image provider '${explicit}'. Valid providers: ${names.join(', ')}`);
|
|
267
|
+
}
|
|
268
|
+
if (!available(reg.name)) {
|
|
269
|
+
throw new Error(notConfiguredMessage(reg));
|
|
270
|
+
}
|
|
271
|
+
return reg.name;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
for (const name of AUTO_ORDER) {
|
|
275
|
+
if (!NEVER_AUTO.has(name) && available(name)) {
|
|
276
|
+
return name;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const wanted = names
|
|
280
|
+
.map((name) => {
|
|
281
|
+
const reg = PROVIDERS[name];
|
|
282
|
+
return reg === undefined ? name : `${name}: ${envSummary(reg)}`;
|
|
283
|
+
})
|
|
284
|
+
.join('; ');
|
|
285
|
+
throw new Error(`No image provider is configured. Credentials wanted — ${wanted}`);
|
|
286
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type { ImageProvider, ImageRequest, ImageResult, ProviderFactory, ProviderOptions } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Z.AI GLM-image adapter (0115 R1) — port of
|
|
5
|
+
* vendors/baoyu-skills/skills/baoyu-image-gen/scripts/providers/zai.ts
|
|
6
|
+
* (MIT © 2026 Jim Liu). Text-to-image only; `CliArgs.size` becomes width/height.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type ZaiModelFamily = 'glm' | 'legacy';
|
|
10
|
+
|
|
11
|
+
const GLM_MAX_PIXELS = 2 ** 22;
|
|
12
|
+
const LEGACY_MAX_PIXELS = 2 ** 21;
|
|
13
|
+
const GLM_SIZE_STEP = 32;
|
|
14
|
+
const LEGACY_SIZE_STEP = 16;
|
|
15
|
+
|
|
16
|
+
const GLM_RECOMMENDED_SIZES: Record<string, string> = {
|
|
17
|
+
'1:1': '1280x1280',
|
|
18
|
+
'3:2': '1568x1056',
|
|
19
|
+
'2:3': '1056x1568',
|
|
20
|
+
'4:3': '1472x1088',
|
|
21
|
+
'3:4': '1088x1472',
|
|
22
|
+
'16:9': '1728x960',
|
|
23
|
+
'9:16': '960x1728',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const LEGACY_RECOMMENDED_SIZES: Record<string, string> = {
|
|
27
|
+
'1:1': '1024x1024',
|
|
28
|
+
'9:16': '768x1344',
|
|
29
|
+
'3:4': '864x1152',
|
|
30
|
+
'16:9': '1344x768',
|
|
31
|
+
'4:3': '1152x864',
|
|
32
|
+
'2:1': '1440x720',
|
|
33
|
+
'1:2': '720x1440',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function buildZaiUrl(base: string): string {
|
|
37
|
+
const cleaned = base.replace(/\/+$/g, '');
|
|
38
|
+
if (cleaned.endsWith('/images/generations')) return cleaned;
|
|
39
|
+
if (cleaned.endsWith('/api/paas/v4') || cleaned.endsWith('/v4')) return `${cleaned}/images/generations`;
|
|
40
|
+
return `${cleaned}/api/paas/v4/images/generations`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getModelFamily(model: string): ZaiModelFamily {
|
|
44
|
+
return model.trim().toLowerCase() === 'glm-image' ? 'glm' : 'legacy';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function parseAspectRatio(ar: string): { width: number; height: number } | null {
|
|
48
|
+
const match = ar.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
|
|
49
|
+
if (match === null) return null;
|
|
50
|
+
const width = Number(match[1]);
|
|
51
|
+
const height = Number(match[2]);
|
|
52
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
|
|
53
|
+
return { width, height };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function parseSize(size: string): { width: number; height: number } | null {
|
|
57
|
+
const match = size.trim().match(/^(\d+)\s*[xX*]\s*(\d+)$/);
|
|
58
|
+
if (match === null) return null;
|
|
59
|
+
const width = Number.parseInt(match[1] as string, 10);
|
|
60
|
+
const height = Number.parseInt(match[2] as string, 10);
|
|
61
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
|
|
62
|
+
return { width, height };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function roundToStep(value: number, step: number): number {
|
|
66
|
+
return Math.max(step, Math.round(value / step) * step);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const DEFAULT_MODEL = 'glm-image';
|
|
70
|
+
|
|
71
|
+
function getRatioValue(ar: string): number | null {
|
|
72
|
+
const parsed = parseAspectRatio(ar);
|
|
73
|
+
return parsed === null ? null : parsed.width / parsed.height;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function findClosestRatioKey(ar: string, candidates: string[]): string | null {
|
|
77
|
+
const targetRatio = getRatioValue(ar);
|
|
78
|
+
if (targetRatio === null) return null;
|
|
79
|
+
let bestKey: string | null = null;
|
|
80
|
+
let bestDiff = Number.POSITIVE_INFINITY;
|
|
81
|
+
for (const candidate of candidates) {
|
|
82
|
+
const candidateRatio = getRatioValue(candidate);
|
|
83
|
+
if (candidateRatio === null) continue;
|
|
84
|
+
const diff = Math.abs(candidateRatio - targetRatio);
|
|
85
|
+
if (diff < bestDiff) {
|
|
86
|
+
bestDiff = diff;
|
|
87
|
+
bestKey = candidate;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return bestDiff <= 0.05 ? bestKey : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function fitToPixelBudget(
|
|
94
|
+
width: number,
|
|
95
|
+
height: number,
|
|
96
|
+
targetPixels: number,
|
|
97
|
+
maxPixels: number,
|
|
98
|
+
step: number,
|
|
99
|
+
): { width: number; height: number } {
|
|
100
|
+
let nextWidth = width;
|
|
101
|
+
let nextHeight = height;
|
|
102
|
+
const pixels = nextWidth * nextHeight;
|
|
103
|
+
const scaleTo = pixels > maxPixels ? maxPixels : targetPixels;
|
|
104
|
+
const scale = Math.sqrt(scaleTo / pixels);
|
|
105
|
+
nextWidth *= scale;
|
|
106
|
+
nextHeight *= scale;
|
|
107
|
+
|
|
108
|
+
let roundedWidth = roundToStep(nextWidth, step);
|
|
109
|
+
let roundedHeight = roundToStep(nextHeight, step);
|
|
110
|
+
let roundedPixels = roundedWidth * roundedHeight;
|
|
111
|
+
while (roundedPixels > maxPixels && (roundedWidth > step || roundedHeight > step)) {
|
|
112
|
+
if (roundedWidth >= roundedHeight && roundedWidth > step) {
|
|
113
|
+
roundedWidth -= step;
|
|
114
|
+
} else if (roundedHeight > step) {
|
|
115
|
+
roundedHeight -= step;
|
|
116
|
+
} else {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
roundedPixels = roundedWidth * roundedHeight;
|
|
120
|
+
}
|
|
121
|
+
return { width: roundedWidth, height: roundedHeight };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function resolveSizeForModel(
|
|
125
|
+
model: string,
|
|
126
|
+
args: { size: string | undefined; aspectRatio: string | undefined; quality: 'normal' | '2k' | undefined },
|
|
127
|
+
): string {
|
|
128
|
+
const family = getModelFamily(model);
|
|
129
|
+
if (args.size !== undefined) {
|
|
130
|
+
const parsed = parseSize(args.size);
|
|
131
|
+
if (parsed === null) throw new Error('Z.AI --size must be in WxH format, for example 1280x1280.');
|
|
132
|
+
const widthStep = family === 'glm' ? GLM_SIZE_STEP : LEGACY_SIZE_STEP;
|
|
133
|
+
const minEdge = family === 'glm' ? 1024 : 512;
|
|
134
|
+
const maxPixels = family === 'glm' ? GLM_MAX_PIXELS : LEGACY_MAX_PIXELS;
|
|
135
|
+
if (parsed.width < minEdge || parsed.width > 2048 || parsed.height < minEdge || parsed.height > 2048) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
family === 'glm'
|
|
138
|
+
? 'GLM-image custom size requires width and height between 1024 and 2048.'
|
|
139
|
+
: 'Z.AI legacy image models require width and height between 512 and 2048.',
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (parsed.width % widthStep !== 0 || parsed.height % widthStep !== 0) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
family === 'glm'
|
|
145
|
+
? 'GLM-image custom size requires width and height divisible by 32.'
|
|
146
|
+
: 'Z.AI legacy image models require width and height divisible by 16.',
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (parsed.width * parsed.height > maxPixels) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
family === 'glm'
|
|
152
|
+
? 'GLM-image custom size must not exceed 2^22 total pixels.'
|
|
153
|
+
: 'Z.AI legacy image size must not exceed 2^21 total pixels.',
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return `${parsed.width}x${parsed.height}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const recommended = family === 'glm' ? GLM_RECOMMENDED_SIZES : LEGACY_RECOMMENDED_SIZES;
|
|
160
|
+
const defaultSize = family === 'glm' ? '1280x1280' : '1024x1024';
|
|
161
|
+
if (args.aspectRatio === undefined) return defaultSize;
|
|
162
|
+
|
|
163
|
+
const recommendedRatio = findClosestRatioKey(args.aspectRatio, Object.keys(recommended));
|
|
164
|
+
if (recommendedRatio !== null) {
|
|
165
|
+
return recommended[recommendedRatio] as string;
|
|
166
|
+
}
|
|
167
|
+
const parsedRatio = parseAspectRatio(args.aspectRatio);
|
|
168
|
+
if (parsedRatio === null) return defaultSize;
|
|
169
|
+
|
|
170
|
+
const targetPixels = args.quality === 'normal' ? 1024 * 1024 : 1536 * 1536;
|
|
171
|
+
const maxPixels = family === 'glm' ? GLM_MAX_PIXELS : LEGACY_MAX_PIXELS;
|
|
172
|
+
const step = family === 'glm' ? GLM_SIZE_STEP : LEGACY_SIZE_STEP;
|
|
173
|
+
const fit = fitToPixelBudget(parsedRatio.width, parsedRatio.height, targetPixels, maxPixels, step);
|
|
174
|
+
return `${fit.width}x${fit.height}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
interface ZaiResponse {
|
|
178
|
+
data?: Array<{ url?: string }>;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const createZai: ProviderFactory = (options?: ProviderOptions): ImageProvider => {
|
|
182
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
183
|
+
const env = options?.env ?? process.env;
|
|
184
|
+
const readEnv = (key: string): string => env[key] ?? '';
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
name: 'zai',
|
|
188
|
+
defaultModel: () => DEFAULT_MODEL,
|
|
189
|
+
isAvailable: () => (readEnv('ZAI_API_KEY') || readEnv('BIGMODEL_API_KEY')) !== '',
|
|
190
|
+
supportsReferenceImages: () => false,
|
|
191
|
+
validate(req: ImageRequest): void {
|
|
192
|
+
if ((req.referenceImages?.length ?? 0) > 0) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
'Z.AI GLM-image currently supports text-to-image only in kk image-gen. Remove --ref or choose another provider.',
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
async generate(req: ImageRequest): Promise<ImageResult> {
|
|
199
|
+
const refs = req.referenceImages ?? [];
|
|
200
|
+
if (refs.length > 0) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
'Z.AI GLM-image currently supports text-to-image only in kk image-gen. Remove --ref or choose another provider.',
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const apiKey = readEnv('ZAI_API_KEY') || readEnv('BIGMODEL_API_KEY');
|
|
206
|
+
if (apiKey === '') throw new Error('ZAI_API_KEY is required. Get one from https://docs.z.ai/.');
|
|
207
|
+
|
|
208
|
+
const size = resolveSizeForModel(req.model, {
|
|
209
|
+
size: req.width !== undefined && req.height !== undefined ? `${req.width}x${req.height}` : undefined,
|
|
210
|
+
aspectRatio: req.aspectRatio,
|
|
211
|
+
quality: req.quality,
|
|
212
|
+
});
|
|
213
|
+
const base = readEnv('ZAI_BASE_URL') || readEnv('BIGMODEL_BASE_URL') || 'https://api.z.ai/api/paas/v4';
|
|
214
|
+
const response = await fetchFn(buildZaiUrl(base), {
|
|
215
|
+
method: 'POST',
|
|
216
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${apiKey}` },
|
|
217
|
+
body: JSON.stringify({
|
|
218
|
+
model: req.model,
|
|
219
|
+
prompt: req.prompt,
|
|
220
|
+
quality: req.quality === 'normal' ? 'standard' : 'hd',
|
|
221
|
+
size,
|
|
222
|
+
}),
|
|
223
|
+
});
|
|
224
|
+
if (!response.ok) throw new Error(`Z.AI API error (${response.status}): ${await response.text()}`);
|
|
225
|
+
const url = ((await response.json()) as ZaiResponse).data?.[0]?.url;
|
|
226
|
+
if (url === undefined) throw new Error('No image URL in Z.AI response');
|
|
227
|
+
const imageResponse = await fetchFn(url);
|
|
228
|
+
if (!imageResponse.ok) throw new Error(`Failed to download image from Z.AI: ${imageResponse.status}`);
|
|
229
|
+
return {
|
|
230
|
+
bytes: new Uint8Array(await imageResponse.arrayBuffer()),
|
|
231
|
+
extension: 'png',
|
|
232
|
+
provider: 'zai',
|
|
233
|
+
model: req.model,
|
|
234
|
+
};
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
};
|