@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.
Files changed (109) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +20 -0
  3. package/package.json +1 -1
  4. package/plugins/generations/content-gen/dist/index.js +20 -0
  5. package/plugins/generations/core-facts-gen/dist/index.js +20 -0
  6. package/plugins/generations/daily-article-gen/dist/index.js +29 -1
  7. package/plugins/generations/daily-article-gen/src/index.ts +13 -1
  8. package/plugins/generations/dailynews-gen/dist/index.js +20 -0
  9. package/plugins/generations/episode-plan-gen/dist/index.js +24 -0
  10. package/plugins/generations/episode-plan-gen/src/index.ts +11 -0
  11. package/plugins/generations/image-gen/dist/index.js +2296 -53
  12. package/plugins/generations/image-gen/src/providers/agnes.ts +110 -0
  13. package/plugins/generations/image-gen/src/providers/azure.ts +153 -0
  14. package/plugins/generations/image-gen/src/providers/codex-cli.ts +170 -0
  15. package/plugins/generations/image-gen/src/providers/dashscope.ts +485 -0
  16. package/plugins/generations/image-gen/src/providers/google.ts +268 -0
  17. package/plugins/generations/image-gen/src/providers/huggingface.ts +59 -0
  18. package/plugins/generations/image-gen/src/providers/jimeng.ts +259 -0
  19. package/plugins/generations/image-gen/src/providers/minimax.ts +171 -0
  20. package/plugins/generations/image-gen/src/providers/openai.ts +319 -0
  21. package/plugins/generations/image-gen/src/providers/openrouter.ts +257 -0
  22. package/plugins/generations/image-gen/src/providers/refs.ts +24 -0
  23. package/plugins/generations/image-gen/src/providers/replicate.ts +279 -0
  24. package/plugins/generations/image-gen/src/providers/seedream.ts +1 -1
  25. package/plugins/generations/image-gen/src/providers/types.ts +52 -51
  26. package/plugins/generations/image-gen/src/providers/zai.ts +237 -0
  27. package/plugins/generations/news-report-gen/dist/index.js +22193 -0
  28. package/plugins/generations/news-report-gen/package.json +17 -0
  29. package/plugins/generations/news-report-gen/plugin.json +7 -0
  30. package/plugins/generations/news-report-gen/src/index.ts +308 -0
  31. package/plugins/generations/news-report-gen/tsconfig.json +4 -0
  32. package/plugins/generations/omni-voice-gen/Makefile +14 -0
  33. package/plugins/generations/omni-voice-gen/README.md +112 -0
  34. package/plugins/generations/omni-voice-gen/bin/omni-voice-gen +2 -0
  35. package/plugins/generations/omni-voice-gen/dist/omni-voice-gen-prr8skpb. +2 -0
  36. package/plugins/generations/omni-voice-gen/dist/omni-voice-gen.js +6 -0
  37. package/plugins/generations/omni-voice-gen/plugin.json +6 -0
  38. package/plugins/generations/omni-voice-gen/profiles.json +12 -0
  39. package/plugins/generations/omni-voice-gen/pyproject.toml +25 -0
  40. package/plugins/generations/omni-voice-gen/scripts/coverage_gate.py +74 -0
  41. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__init__.py +1 -0
  42. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__main__.py +39 -0
  43. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/audio.py +190 -0
  44. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/backend.py +150 -0
  45. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/contract.py +76 -0
  46. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/mp3.py +60 -0
  47. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +289 -0
  48. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/profiles.py +100 -0
  49. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/qc.py +234 -0
  50. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +352 -0
  51. package/plugins/generations/omni-voice-gen/uv.lock +3510 -0
  52. package/plugins/generations/voice-gen/dist/index.js +30 -2
  53. package/plugins/generations/voice-gen/src/index.ts +16 -1
  54. package/plugins/generations/voice-gen/src/voicebox-client.ts +3 -1
  55. package/plugins/ingestions/aihot-ingest/dist/index.js +20 -0
  56. package/plugins/ingestions/horizon-ingest/dist/index.js +20 -0
  57. package/plugins/ingestions/last30days-ingest/dist/index.js +20 -0
  58. package/plugins/ingestions/web-search/dist/index.js +20 -0
  59. package/plugins/kk/commands/image-extract.md +40 -0
  60. package/plugins/kk/commands/image-generate.md +18 -1
  61. package/plugins/kk/plugin.json +1 -1
  62. package/plugins/kk/skills/image-authoring/SKILL.md +10 -3
  63. package/plugins/kk/skills/image-authoring/references/format-drafting.md +57 -0
  64. package/plugins/kk/skills/image-authoring/references/style-extraction.md +14 -9
  65. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +130 -30
  66. package/plugins/publishings/emdash-pub/dist/index.js +20 -0
  67. package/plugins/publishings/podcast-pub/dist/index.js +57 -3
  68. package/plugins/publishings/podcast-pub/src/index.ts +18 -2
  69. package/plugins/publishings/podcast-pub/src/show-notes.ts +56 -9
  70. package/plugins/publishings/qiita-pub/dist/index.js +20 -0
  71. package/plugins/publishings/surfdash-pub/dist/index.js +96 -6
  72. package/plugins/publishings/surfdash-pub/src/index.ts +109 -9
  73. package/plugins/publishings/zenn-pub/dist/index.js +20 -0
  74. package/plugins/sp/scripts/batch-preflight.mjs +346 -0
  75. package/plugins/sp/scripts/batch-preflight.ts +459 -0
  76. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +615 -0
  77. package/plugins/sp/scripts/daily-summary/daily-summary.ts +846 -0
  78. package/plugins/sp/scripts/daily-summary/logger.ts +28 -0
  79. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +223 -0
  80. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +367 -0
  81. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +132 -0
  82. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +169 -0
  83. package/plugins/sp/scripts/feature-dev-precheck.mjs +171 -0
  84. package/plugins/sp/scripts/feature-dev-precheck.ts +238 -0
  85. package/plugins/sp/scripts/feature-sync-bounded.mjs +285 -0
  86. package/plugins/sp/scripts/feature-sync-bounded.ts +478 -0
  87. package/plugins/sp/scripts/history-anatomy-cache.mjs +902 -0
  88. package/plugins/sp/scripts/history-anatomy-cache.ts +1028 -0
  89. package/plugins/sp/scripts/idea-handoff.mjs +22 -0
  90. package/plugins/sp/scripts/idea-handoff.ts +44 -0
  91. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +185 -0
  92. package/plugins/sp/scripts/inline-run-setup.ts +198 -0
  93. package/plugins/sp/scripts/pr-reviewing.mjs +769 -0
  94. package/plugins/sp/scripts/pr-reviewing.ts +925 -0
  95. package/plugins/sp/scripts/quality-gate.mjs +179 -0
  96. package/plugins/sp/scripts/quality-gate.ts +217 -0
  97. package/plugins/sp/scripts/script-contract-check.ts +319 -0
  98. package/plugins/sp/scripts/stage-registry-adapter.ts +1533 -0
  99. package/plugins/sp/scripts/surface-drift-inventory.ts +929 -0
  100. package/plugins/sp/scripts/task-evidence-precheck.ts +181 -0
  101. package/plugins/sp/scripts/task-size-precheck.ts +175 -0
  102. package/plugins/sp/scripts/transition-shim-check.ts +238 -0
  103. package/plugins/sp/scripts/validate-commands.ts +689 -0
  104. package/plugins/sp/scripts/validate-flag-contracts.ts +878 -0
  105. package/plugins/sp/scripts/verify-answer-lint.ts +530 -0
  106. package/plugins/sp/scripts/workflow-step-profile.mjs +316 -0
  107. package/plugins/sp/scripts/workflow-step-profile.ts +456 -0
  108. package/plugins/sp/scripts/wrapup-steps.mjs +373 -0
  109. 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: defineProvider({
105
+ agnes: {
123
106
  name: 'agnes',
124
107
  env: ['AGNES_API_KEY'],
125
108
  defaultModel: 'agnes-image-2.1-flash',
126
- }),
127
- azure: defineProvider({
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
- 'codex-cli': defineProvider({
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
- dashscope: defineProvider({
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
- google: defineProvider({
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
- jimeng: defineProvider({
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
- minimax: defineProvider({
155
+ create: createJimeng,
156
+ },
157
+ minimax: {
163
158
  name: 'minimax',
164
159
  env: ['MINIMAX_API_KEY'],
165
160
  defaultModel: 'image-01',
166
- }),
167
- openai: defineProvider({
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
- openrouter: defineProvider({
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
- replicate: defineProvider({
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: defineProvider({
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
+ };