@gobing-ai/knowledge-kit 0.0.13 → 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 +20 -0
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +20 -0
- package/plugins/generations/core-facts-gen/dist/index.js +20 -0
- package/plugins/generations/daily-article-gen/dist/index.js +29 -1
- package/plugins/generations/daily-article-gen/src/index.ts +13 -1
- package/plugins/generations/dailynews-gen/dist/index.js +20 -0
- package/plugins/generations/episode-plan-gen/dist/index.js +24 -0
- package/plugins/generations/episode-plan-gen/src/index.ts +11 -0
- package/plugins/generations/image-gen/dist/index.js +2296 -53
- 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 +1 -1
- package/plugins/generations/image-gen/src/providers/types.ts +52 -51
- package/plugins/generations/image-gen/src/providers/zai.ts +237 -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 +30 -2
- 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 +20 -0
- package/plugins/ingestions/horizon-ingest/dist/index.js +20 -0
- package/plugins/ingestions/last30days-ingest/dist/index.js +20 -0
- package/plugins/ingestions/web-search/dist/index.js +20 -0
- package/plugins/kk/commands/image-extract.md +40 -0
- package/plugins/kk/commands/image-generate.md +18 -1
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/skills/image-authoring/SKILL.md +10 -3
- package/plugins/kk/skills/image-authoring/references/format-drafting.md +57 -0
- package/plugins/kk/skills/image-authoring/references/style-extraction.md +14 -9
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +130 -30
- package/plugins/publishings/emdash-pub/dist/index.js +20 -0
- package/plugins/publishings/podcast-pub/dist/index.js +57 -3
- 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 +20 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +96 -6
- package/plugins/publishings/surfdash-pub/src/index.ts +109 -9
- package/plugins/publishings/zenn-pub/dist/index.js +20 -0
- 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
|
@@ -9,7 +9,19 @@
|
|
|
9
9
|
* are new, `ImageResult` wraps the bytes — are recorded in the task Design.
|
|
10
10
|
*/
|
|
11
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';
|
|
12
23
|
import { createSeedream } from './seedream';
|
|
24
|
+
import { createZai } from './zai';
|
|
13
25
|
|
|
14
26
|
/** The two quality levels baoyu exposes (SKILL.md); every provider maps or ignores them. */
|
|
15
27
|
export type ImageQuality = 'normal' | '2k';
|
|
@@ -88,60 +100,35 @@ interface ProviderSpec {
|
|
|
88
100
|
|
|
89
101
|
type ProviderRegistration = ProviderSpec & { readonly create: ProviderFactory };
|
|
90
102
|
|
|
91
|
-
/**
|
|
92
|
-
* Env-only shell: makes the frozen registry executable (`isAvailable` /
|
|
93
|
-
* `supportsReferenceImages` answer from the inventory facts) before the adapter tickets
|
|
94
|
-
* land; `generate` throws until then. An adapter ticket replaces `create` with the real
|
|
95
|
-
* wire-speaking factory. A spec with no env criteria (codex-cli) reports not-ready — its
|
|
96
|
-
* readiness (codex on PATH) is not env-expressible.
|
|
97
|
-
*/
|
|
98
|
-
function envOnlyProvider(spec: ProviderSpec): ProviderFactory {
|
|
99
|
-
return (options) => ({
|
|
100
|
-
name: spec.name,
|
|
101
|
-
defaultModel: () => spec.defaultModel,
|
|
102
|
-
isAvailable: () => {
|
|
103
|
-
const env = options?.env ?? process.env;
|
|
104
|
-
const present = (key: string): boolean => (env[key] ?? '') !== '';
|
|
105
|
-
const anyOf = spec.env.length > 0 ? spec.env.some(present) : spec.envAll !== undefined;
|
|
106
|
-
return anyOf && (spec.envAll ?? []).every(present);
|
|
107
|
-
},
|
|
108
|
-
supportsReferenceImages: (model) =>
|
|
109
|
-
spec.refModels === undefined ? true : spec.refModels.some((fragment) => model.includes(fragment)),
|
|
110
|
-
generate: () => {
|
|
111
|
-
throw new Error(`Provider '${spec.name}' has no adapter yet (feature K phase plan, task 0103 Design).`);
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function defineProvider(spec: ProviderSpec): ProviderRegistration {
|
|
117
|
-
return { ...spec, create: envOnlyProvider(spec) };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
103
|
/** The 12 baoyu adapters, one row each (task 0103 R1 inventory). Keys sorted alphabetically. */
|
|
121
104
|
export const PROVIDERS: Readonly<Record<string, ProviderRegistration>> = {
|
|
122
|
-
agnes:
|
|
105
|
+
agnes: {
|
|
123
106
|
name: 'agnes',
|
|
124
107
|
env: ['AGNES_API_KEY'],
|
|
125
108
|
defaultModel: 'agnes-image-2.1-flash',
|
|
126
|
-
|
|
127
|
-
|
|
109
|
+
create: createAgnes,
|
|
110
|
+
},
|
|
111
|
+
azure: {
|
|
128
112
|
name: 'azure',
|
|
129
113
|
env: ['AZURE_OPENAI_API_KEY'],
|
|
130
114
|
defaultModel: 'gpt-image-2',
|
|
131
|
-
|
|
132
|
-
|
|
115
|
+
create: createAzure,
|
|
116
|
+
},
|
|
117
|
+
'codex-cli': {
|
|
133
118
|
name: 'codex-cli',
|
|
134
119
|
env: [],
|
|
135
120
|
defaultModel: 'codex-image-gen',
|
|
136
121
|
note: "requires the 'codex' CLI on PATH with an active login (never auto-selected)",
|
|
137
|
-
|
|
138
|
-
|
|
122
|
+
create: createCodexCli,
|
|
123
|
+
},
|
|
124
|
+
dashscope: {
|
|
139
125
|
name: 'dashscope',
|
|
140
126
|
env: ['DASHSCOPE_API_KEY'],
|
|
141
127
|
defaultModel: 'qwen-image-2.0-pro',
|
|
142
128
|
refModels: ['wan2.7'],
|
|
143
|
-
|
|
144
|
-
|
|
129
|
+
create: createDashscope,
|
|
130
|
+
},
|
|
131
|
+
google: {
|
|
145
132
|
name: 'google',
|
|
146
133
|
env: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
|
|
147
134
|
defaultModel: 'gemini-3-pro-image',
|
|
@@ -152,34 +139,46 @@ export const PROVIDERS: Readonly<Record<string, ProviderRegistration>> = {
|
|
|
152
139
|
'gemini-3-flash-preview',
|
|
153
140
|
'gemini-3.1-flash-image-preview',
|
|
154
141
|
],
|
|
155
|
-
|
|
156
|
-
|
|
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: {
|
|
157
151
|
name: 'jimeng',
|
|
158
152
|
env: [],
|
|
159
153
|
envAll: ['JIMENG_ACCESS_KEY_ID', 'JIMENG_SECRET_ACCESS_KEY'],
|
|
160
154
|
defaultModel: 'jimeng_t2i_v40',
|
|
161
|
-
|
|
162
|
-
|
|
155
|
+
create: createJimeng,
|
|
156
|
+
},
|
|
157
|
+
minimax: {
|
|
163
158
|
name: 'minimax',
|
|
164
159
|
env: ['MINIMAX_API_KEY'],
|
|
165
160
|
defaultModel: 'image-01',
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
create: createMinimax,
|
|
162
|
+
},
|
|
163
|
+
openai: {
|
|
168
164
|
name: 'openai',
|
|
169
165
|
env: ['OPENAI_API_KEY'],
|
|
170
166
|
defaultModel: 'gpt-image-2',
|
|
171
167
|
refModels: ['gpt-image'],
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
create: createOpenai,
|
|
169
|
+
},
|
|
170
|
+
openrouter: {
|
|
174
171
|
name: 'openrouter',
|
|
175
172
|
env: ['OPENROUTER_API_KEY'],
|
|
176
173
|
defaultModel: 'google/gemini-3.1-flash-image',
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
create: createOpenrouter,
|
|
175
|
+
},
|
|
176
|
+
replicate: {
|
|
179
177
|
name: 'replicate',
|
|
180
178
|
env: ['REPLICATE_API_TOKEN'],
|
|
181
179
|
defaultModel: 'google/nano-banana-2',
|
|
182
|
-
|
|
180
|
+
create: createReplicate,
|
|
181
|
+
},
|
|
183
182
|
seedream: {
|
|
184
183
|
name: 'seedream',
|
|
185
184
|
env: ['ARK_API_KEY'],
|
|
@@ -188,16 +187,18 @@ export const PROVIDERS: Readonly<Record<string, ProviderRegistration>> = {
|
|
|
188
187
|
// 0107: the first wire-speaking adapter — replaces the envOnly shell.
|
|
189
188
|
create: createSeedream,
|
|
190
189
|
},
|
|
191
|
-
zai:
|
|
190
|
+
zai: {
|
|
192
191
|
name: 'zai',
|
|
193
192
|
env: ['ZAI_API_KEY', 'BIGMODEL_API_KEY'],
|
|
194
193
|
defaultModel: 'glm-image',
|
|
195
194
|
refModels: [],
|
|
196
|
-
|
|
195
|
+
create: createZai,
|
|
196
|
+
},
|
|
197
197
|
};
|
|
198
198
|
|
|
199
199
|
/** Frozen auto-selection priority (task 0103 Design). `codex-cli` is absent on purpose. */
|
|
200
200
|
export const AUTO_ORDER = [
|
|
201
|
+
'huggingface',
|
|
201
202
|
'google',
|
|
202
203
|
'openai',
|
|
203
204
|
'replicate',
|
|
@@ -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
|
+
};
|