dsh-coding-subscription-oauth 0.5.0

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 (178) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/CONTRIBUTING.md +109 -0
  3. package/INSTALL.md +220 -0
  4. package/LICENSE +19 -0
  5. package/NOTICE +11 -0
  6. package/README.de.md +254 -0
  7. package/README.es.md +254 -0
  8. package/README.fr.md +254 -0
  9. package/README.ja.md +254 -0
  10. package/README.ko.md +254 -0
  11. package/README.md +286 -0
  12. package/README.pt-BR.md +254 -0
  13. package/README.ru.md +254 -0
  14. package/README.zh-CN.md +271 -0
  15. package/cordis.patch.yml +13 -0
  16. package/docs/00-project-rules.md +195 -0
  17. package/docs/02-architecture.md +132 -0
  18. package/docs/02-architecture.zh-CN.md +128 -0
  19. package/lib/adapter.d.ts +24 -0
  20. package/lib/adapter.d.ts.map +1 -0
  21. package/lib/alias-adapter.d.ts +43 -0
  22. package/lib/alias-adapter.d.ts.map +1 -0
  23. package/lib/auth-routes.d.ts +155 -0
  24. package/lib/auth-routes.d.ts.map +1 -0
  25. package/lib/auth.d.ts +29 -0
  26. package/lib/auth.d.ts.map +1 -0
  27. package/lib/bin.d.ts +6 -0
  28. package/lib/bin.d.ts.map +1 -0
  29. package/lib/bin.js +27591 -0
  30. package/lib/bin.js.map +7 -0
  31. package/lib/capability-routes.d.ts +43 -0
  32. package/lib/capability-routes.d.ts.map +1 -0
  33. package/lib/capability-runtime.d.ts +56 -0
  34. package/lib/capability-runtime.d.ts.map +1 -0
  35. package/lib/capability-settings.d.ts +263 -0
  36. package/lib/capability-settings.d.ts.map +1 -0
  37. package/lib/capability-tools.d.ts +50 -0
  38. package/lib/capability-tools.d.ts.map +1 -0
  39. package/lib/catalog.d.ts +53 -0
  40. package/lib/catalog.d.ts.map +1 -0
  41. package/lib/client.js +3 -0
  42. package/lib/client.js.map +7 -0
  43. package/lib/codex-http.d.ts +77 -0
  44. package/lib/codex-http.d.ts.map +1 -0
  45. package/lib/codex-images.d.ts +109 -0
  46. package/lib/codex-images.d.ts.map +1 -0
  47. package/lib/codex-model-capabilities.d.ts +112 -0
  48. package/lib/codex-model-capabilities.d.ts.map +1 -0
  49. package/lib/codex-search.d.ts +96 -0
  50. package/lib/codex-search.d.ts.map +1 -0
  51. package/lib/codex-usage.d.ts +79 -0
  52. package/lib/codex-usage.d.ts.map +1 -0
  53. package/lib/gateway-anthropic-messages.d.ts +8 -0
  54. package/lib/gateway-anthropic-messages.d.ts.map +1 -0
  55. package/lib/gateway-auth.d.ts +22 -0
  56. package/lib/gateway-auth.d.ts.map +1 -0
  57. package/lib/gateway-backend.d.ts +62 -0
  58. package/lib/gateway-backend.d.ts.map +1 -0
  59. package/lib/gateway-body.d.ts +9 -0
  60. package/lib/gateway-body.d.ts.map +1 -0
  61. package/lib/gateway-config.d.ts +24 -0
  62. package/lib/gateway-config.d.ts.map +1 -0
  63. package/lib/gateway-http.d.ts +16 -0
  64. package/lib/gateway-http.d.ts.map +1 -0
  65. package/lib/gateway-openai-chat.d.ts +8 -0
  66. package/lib/gateway-openai-chat.d.ts.map +1 -0
  67. package/lib/gateway-openai-responses.d.ts +8 -0
  68. package/lib/gateway-openai-responses.d.ts.map +1 -0
  69. package/lib/gateway-parse.d.ts +10 -0
  70. package/lib/gateway-parse.d.ts.map +1 -0
  71. package/lib/gateway-protocol.d.ts +47 -0
  72. package/lib/gateway-protocol.d.ts.map +1 -0
  73. package/lib/gateway-routes.d.ts +21 -0
  74. package/lib/gateway-routes.d.ts.map +1 -0
  75. package/lib/gateway.d.ts +48 -0
  76. package/lib/gateway.d.ts.map +1 -0
  77. package/lib/grok-imagine.d.ts +271 -0
  78. package/lib/grok-imagine.d.ts.map +1 -0
  79. package/lib/grok-import.d.ts +21 -0
  80. package/lib/grok-import.d.ts.map +1 -0
  81. package/lib/http-json.d.ts +10 -0
  82. package/lib/http-json.d.ts.map +1 -0
  83. package/lib/ids.d.ts +33 -0
  84. package/lib/ids.d.ts.map +1 -0
  85. package/lib/imagine-routes.d.ts +59 -0
  86. package/lib/imagine-routes.d.ts.map +1 -0
  87. package/lib/index.d.ts +69 -0
  88. package/lib/index.d.ts.map +1 -0
  89. package/lib/index.js +35355 -0
  90. package/lib/index.js.map +7 -0
  91. package/lib/invariant.d.ts +9 -0
  92. package/lib/invariant.d.ts.map +1 -0
  93. package/lib/invariant.js +14 -0
  94. package/lib/invariant.js.map +7 -0
  95. package/lib/kimi-errors.d.ts +13 -0
  96. package/lib/kimi-errors.d.ts.map +1 -0
  97. package/lib/media-store.d.ts +130 -0
  98. package/lib/media-store.d.ts.map +1 -0
  99. package/lib/oauth-import-routes.d.ts +52 -0
  100. package/lib/oauth-import-routes.d.ts.map +1 -0
  101. package/lib/oauth-providers.d.ts +26 -0
  102. package/lib/oauth-providers.d.ts.map +1 -0
  103. package/lib/oauth-session.d.ts +40 -0
  104. package/lib/oauth-session.d.ts.map +1 -0
  105. package/lib/oauth-sources.d.ts +205 -0
  106. package/lib/oauth-sources.d.ts.map +1 -0
  107. package/lib/oauth.d.ts +79 -0
  108. package/lib/oauth.d.ts.map +1 -0
  109. package/lib/provider.d.ts +38 -0
  110. package/lib/provider.d.ts.map +1 -0
  111. package/lib/proxy.d.ts +17 -0
  112. package/lib/proxy.d.ts.map +1 -0
  113. package/lib/redact.d.ts +5 -0
  114. package/lib/redact.d.ts.map +1 -0
  115. package/lib/session.d.ts +40 -0
  116. package/lib/session.d.ts.map +1 -0
  117. package/lib/store.d.ts +46 -0
  118. package/lib/store.d.ts.map +1 -0
  119. package/lib/web-origin.d.ts +10 -0
  120. package/lib/web-origin.d.ts.map +1 -0
  121. package/lib/web-routes.d.ts +20 -0
  122. package/lib/web-routes.d.ts.map +1 -0
  123. package/package.json +185 -0
  124. package/patches/dsh-agy@0.1.2.patch +25 -0
  125. package/scripts/release.mjs +166 -0
  126. package/scripts/smoke-deployed-routes.mjs +146 -0
  127. package/scripts/verify-deployed-catalog.mjs +87 -0
  128. package/src/adapter.ts +282 -0
  129. package/src/alias-adapter.ts +152 -0
  130. package/src/auth-routes.ts +871 -0
  131. package/src/auth.ts +67 -0
  132. package/src/bin.ts +350 -0
  133. package/src/capability-routes.ts +275 -0
  134. package/src/capability-runtime.ts +313 -0
  135. package/src/capability-settings.ts +657 -0
  136. package/src/capability-tools.ts +666 -0
  137. package/src/catalog.ts +271 -0
  138. package/src/client/GrokBuildSettings.tsx +2221 -0
  139. package/src/client/index.tsx +37 -0
  140. package/src/client/locales.ts +421 -0
  141. package/src/codex-http.ts +447 -0
  142. package/src/codex-images.ts +485 -0
  143. package/src/codex-model-capabilities.ts +320 -0
  144. package/src/codex-search.ts +245 -0
  145. package/src/codex-usage.ts +263 -0
  146. package/src/gateway-anthropic-messages.ts +84 -0
  147. package/src/gateway-auth.ts +100 -0
  148. package/src/gateway-backend.ts +274 -0
  149. package/src/gateway-body.ts +49 -0
  150. package/src/gateway-config.ts +76 -0
  151. package/src/gateway-http.ts +104 -0
  152. package/src/gateway-openai-chat.ts +124 -0
  153. package/src/gateway-openai-responses.ts +53 -0
  154. package/src/gateway-parse.ts +224 -0
  155. package/src/gateway-protocol.ts +52 -0
  156. package/src/gateway-routes.ts +152 -0
  157. package/src/gateway.ts +242 -0
  158. package/src/grok-imagine.ts +1627 -0
  159. package/src/grok-import.ts +151 -0
  160. package/src/http-json.ts +82 -0
  161. package/src/ids.ts +45 -0
  162. package/src/imagine-routes.ts +461 -0
  163. package/src/index.ts +598 -0
  164. package/src/invariant.ts +17 -0
  165. package/src/kimi-errors.ts +26 -0
  166. package/src/media-store.ts +927 -0
  167. package/src/oauth-import-routes.ts +314 -0
  168. package/src/oauth-providers.ts +152 -0
  169. package/src/oauth-session.ts +183 -0
  170. package/src/oauth-sources.ts +1104 -0
  171. package/src/oauth.ts +620 -0
  172. package/src/provider.ts +128 -0
  173. package/src/proxy.ts +99 -0
  174. package/src/redact.ts +72 -0
  175. package/src/session.ts +218 -0
  176. package/src/store.ts +217 -0
  177. package/src/web-origin.ts +60 -0
  178. package/src/web-routes.ts +75 -0
@@ -0,0 +1,666 @@
1
+ /**
2
+ * Optional Codex / Grok Imagine tool definitions. The factory only returns
3
+ * public `ToolDefinition` objects — it never registers on `ctx.tools`.
4
+ * Feature flags and `imageCount` are re-read from `current()` at execute time.
5
+ * @module dsh-coding-subscription-oauth/capability-tools
6
+ */
7
+
8
+ import type { ImageAttachmentRef } from "@deepseek-ai/dsh-attachment";
9
+ import { LlmError } from "@deepseek-ai/dsh-llm";
10
+ import type { ToolDefinition, ToolRunContext } from "@deepseek-ai/dsh-tools";
11
+ import { CAPABILITY_SETTINGS_BOUNDS, type CapabilitySettings } from "./capability-settings.ts";
12
+ import type { CodexAuthSession } from "./codex-http.ts";
13
+ import {
14
+ CODEX_IMAGE_BACKGROUNDS,
15
+ CODEX_IMAGE_MODEL,
16
+ CODEX_IMAGE_PROMPT_MAX_LENGTH,
17
+ CODEX_IMAGE_QUALITIES,
18
+ CODEX_IMAGE_SIZES,
19
+ type CodexImageAttachmentStore,
20
+ type CodexImageBackground,
21
+ type CodexImageController,
22
+ type CodexImageQuality,
23
+ type CodexImageResult,
24
+ type CodexImageRoute,
25
+ type CodexImageSessionContext,
26
+ type CodexImageSize,
27
+ createCodexImageController,
28
+ } from "./codex-images.ts";
29
+ import {
30
+ type GenerateImagineImageInput,
31
+ GROK_IMAGINE_IMAGE_MODEL,
32
+ GROK_IMAGINE_IMAGE_TOOL,
33
+ GROK_IMAGINE_VIDEO_MODEL,
34
+ GROK_IMAGINE_VIDEO_STATUS_TOOL,
35
+ GROK_IMAGINE_VIDEO_TOOL,
36
+ type GrokImagineClient,
37
+ IMAGINE_IMAGE_ASPECT_RATIOS,
38
+ IMAGINE_IMAGE_RESOLUTIONS,
39
+ IMAGINE_PROMPT_MAX_LENGTH,
40
+ IMAGINE_VIDEO_ASPECT_RATIOS,
41
+ IMAGINE_VIDEO_MAX_DURATION_SECONDS,
42
+ IMAGINE_VIDEO_MIN_DURATION_SECONDS,
43
+ IMAGINE_VIDEO_RESOLUTIONS,
44
+ type ImagineImageAspectRatio,
45
+ type ImagineImageResolution,
46
+ type ImagineImageResult,
47
+ type ImagineVideoAspectRatio,
48
+ type ImagineVideoResolution,
49
+ type ImagineVideoStartResult,
50
+ type ImagineVideoStatusResult,
51
+ imagineImagePath,
52
+ parseVideoRequestId,
53
+ type StartImagineVideoInput,
54
+ } from "./grok-imagine.ts";
55
+ import { imagineMediaPath, type MediaArtifactMeta } from "./media-store.ts";
56
+
57
+ export const CODEX_IMAGE_GENERATE_TOOL = "codex_image_generate";
58
+ export const CODEX_IMAGE_EDIT_TOOL = "codex_image_edit";
59
+
60
+ export {
61
+ GROK_IMAGINE_IMAGE_TOOL,
62
+ GROK_IMAGINE_VIDEO_STATUS_TOOL,
63
+ GROK_IMAGINE_VIDEO_TOOL,
64
+ } from "./grok-imagine.ts";
65
+
66
+ const IMAGE_COUNT_VALUES = [1, 2, 3, 4] as const;
67
+ const VIDEO_DURATION_VALUES = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] as const;
68
+ const IMAGINE_IMAGE_PREFIX = "/plugins/dsh-grok-build/imagine/images/";
69
+ const IMAGINE_MEDIA_PREFIX = "/plugins/dsh-grok-build/imagine/media/";
70
+
71
+ /** Shared client surface; production passes one `GrokImagineClient` so video status can see started jobs. */
72
+ export type CapabilityImagineClient = Pick<GrokImagineClient, "generateImage" | "startVideo" | "videoStatus">;
73
+
74
+ /** Per-exec Codex controller factory. Tests inject a fake; production binds auth + attachments. */
75
+ export type CreateCodexImageController = (session: CodexImageSessionContext) => CodexImageController;
76
+
77
+ /** Resolve authoritative host model metadata for the calling route. */
78
+ export type ResolveCodexImageRoute = (exec: ToolRunContext) => Promise<CodexImageRoute | undefined>;
79
+
80
+ /** Exact provider/model identity from the live request header, then agent options. */
81
+ export function callingRouteIdentity(exec: ToolRunContext): { provider: string; model: string } | undefined {
82
+ const session = exec.agent?.session;
83
+ const header =
84
+ session !== undefined && typeof session.requestHeader === "function" ? session.requestHeader()?.config : undefined;
85
+ const provider = header?.provider ?? exec.agent?.options.provider;
86
+ const model = header?.model ?? exec.agent?.options.model;
87
+ if (typeof provider !== "string" || provider.length === 0) return undefined;
88
+ if (typeof model !== "string" || model.length === 0) return undefined;
89
+ return { provider, model };
90
+ }
91
+
92
+ /**
93
+ * Copy host-resolved modalities onto the calling identity. Never invents
94
+ * `inputModalities`; lookup failures keep the identity and omit the field so
95
+ * the image-capability gate fails closed.
96
+ */
97
+ export async function resolveCodexImageRouteFromLlm(
98
+ exec: ToolRunContext,
99
+ resolveModelInfo: (
100
+ provider: string,
101
+ model: string,
102
+ signal?: AbortSignal,
103
+ ) => Promise<{ inputModalities?: readonly string[] }>,
104
+ ): Promise<CodexImageRoute | undefined> {
105
+ const identity = callingRouteIdentity(exec);
106
+ if (identity === undefined) return undefined;
107
+ try {
108
+ const info = await resolveModelInfo(identity.provider, identity.model, exec.signal);
109
+ const modalities = info.inputModalities;
110
+ return {
111
+ ...identity,
112
+ ...(Array.isArray(modalities) ? { inputModalities: [...modalities] } : {}),
113
+ };
114
+ } catch {
115
+ return identity;
116
+ }
117
+ }
118
+
119
+ export interface CapabilityToolsOptions {
120
+ /** Live capability section. Re-read on every execute so a disable takes effect immediately. */
121
+ current(): CapabilitySettings;
122
+ readonly auth: CodexAuthSession;
123
+ readonly attachments: CodexImageAttachmentStore;
124
+ readonly imagine: CapabilityImagineClient;
125
+ readonly createCodexController?: CreateCodexImageController;
126
+ readonly resolveCodexImageRoute?: ResolveCodexImageRoute;
127
+ }
128
+
129
+ const attachmentRefSchema = {
130
+ type: "object" as const,
131
+ additionalProperties: false as const,
132
+ properties: {
133
+ attachmentId: { type: "string" as const, required: true as const },
134
+ mediaType: { type: "string" as const, required: true as const },
135
+ bytes: { type: "integer" as const, required: true as const },
136
+ width: { type: "integer" as const, required: true as const },
137
+ height: { type: "integer" as const, required: true as const },
138
+ name: { type: "string" as const },
139
+ },
140
+ };
141
+
142
+ /**
143
+ * Shared schema fragment for the Imagine video `requestId`. The host value
144
+ * schema DSL cannot express string patterns, so execute-time validation keeps
145
+ * the authoritative `^[A-Za-z0-9_-]{1,256}$` boundary.
146
+ */
147
+ const videoRequestIdSchema = {
148
+ type: "string" as const,
149
+ required: true as const,
150
+ };
151
+
152
+ /** Shared prompt fragment; execute-time clients reject empty/oversized input. */
153
+ const promptParameter = {
154
+ type: "string" as const,
155
+ required: true as const,
156
+ };
157
+
158
+ const warningSchema = {
159
+ type: "object" as const,
160
+ additionalProperties: false as const,
161
+ properties: {
162
+ index: { type: "integer" as const, required: true as const },
163
+ code: { type: "string" as const, required: true as const },
164
+ message: { type: "string" as const, required: true as const },
165
+ },
166
+ };
167
+
168
+ const artifactSchema = {
169
+ type: "object" as const,
170
+ additionalProperties: false as const,
171
+ properties: {
172
+ artifactId: { type: "string" as const, required: true as const },
173
+ mediaType: { type: "string" as const, required: true as const },
174
+ bytes: { type: "integer" as const, required: true as const },
175
+ createdAt: { type: "integer" as const, required: true as const },
176
+ expiresAt: { type: "integer" as const, required: true as const },
177
+ name: { type: "string" as const },
178
+ },
179
+ };
180
+
181
+ const imagineImageItemSchema = {
182
+ type: "object" as const,
183
+ additionalProperties: false as const,
184
+ properties: {
185
+ attachment: { ...attachmentRefSchema, required: true as const },
186
+ path: { type: "string" as const, required: true as const },
187
+ },
188
+ };
189
+
190
+ function disabled(name: string): never {
191
+ throw new LlmError(`${name} is disabled`, "INVALID_ARGS");
192
+ }
193
+
194
+ function imageCountLimit(settings: CapabilitySettings): number {
195
+ const value = settings.imageCount;
196
+ if (!Number.isSafeInteger(value) || value < CAPABILITY_SETTINGS_BOUNDS.imageCount.min) {
197
+ return CAPABILITY_SETTINGS_BOUNDS.imageCount.default;
198
+ }
199
+ return Math.min(value, CAPABILITY_SETTINGS_BOUNDS.imageCount.max);
200
+ }
201
+
202
+ function resolveImageCount(n: number | undefined, settings: CapabilitySettings): number {
203
+ const limit = imageCountLimit(settings);
204
+ const value = n ?? limit;
205
+ if (!Number.isSafeInteger(value) || value < 1 || value > limit) {
206
+ throw new LlmError(`n cannot exceed the configured imageCount (${String(limit)})`, "INVALID_ARGS");
207
+ }
208
+ return value;
209
+ }
210
+
211
+ async function routeFromExec(
212
+ exec: ToolRunContext,
213
+ resolve: ResolveCodexImageRoute | undefined,
214
+ ): Promise<CodexImageRoute | undefined> {
215
+ if (resolve === undefined) return undefined;
216
+ try {
217
+ const resolved = await resolve(exec);
218
+ if (resolved === undefined) return undefined;
219
+ const modalities = resolved.inputModalities;
220
+ return {
221
+ ...(typeof resolved.provider === "string" && resolved.provider.length > 0 ? { provider: resolved.provider } : {}),
222
+ ...(typeof resolved.model === "string" && resolved.model.length > 0 ? { model: resolved.model } : {}),
223
+ ...(Array.isArray(modalities) ? { inputModalities: [...modalities] } : {}),
224
+ };
225
+ } catch {
226
+ return undefined;
227
+ }
228
+ }
229
+
230
+ async function generateSession(
231
+ exec: ToolRunContext,
232
+ resolve: ResolveCodexImageRoute | undefined,
233
+ ): Promise<CodexImageSessionContext> {
234
+ const route = await routeFromExec(exec, resolve);
235
+ return {
236
+ deriveMessages: () => [],
237
+ ...(route === undefined ? {} : { route }),
238
+ };
239
+ }
240
+
241
+ async function requireEditSession(
242
+ exec: ToolRunContext,
243
+ resolve: ResolveCodexImageRoute | undefined,
244
+ ): Promise<CodexImageSessionContext> {
245
+ const session = exec.agent?.session;
246
+ if (session === undefined || typeof session.deriveMessages !== "function") {
247
+ throw new LlmError("codex_image_edit requires an active agent session", "INVALID_ARGS");
248
+ }
249
+ const route = await routeFromExec(exec, resolve);
250
+ return {
251
+ deriveMessages: () => session.deriveMessages(),
252
+ ...(route === undefined ? {} : { route }),
253
+ };
254
+ }
255
+
256
+ function publicPluginPath(path: string | undefined, fallback: string, prefix: string): string {
257
+ if (typeof path === "string" && path.startsWith(prefix)) return path;
258
+ return fallback;
259
+ }
260
+
261
+ function publicAttachmentRef(ref: {
262
+ attachmentId: string;
263
+ mediaType: string;
264
+ bytes: number;
265
+ width: number;
266
+ height: number;
267
+ name?: string;
268
+ }): ImageAttachmentRef {
269
+ return {
270
+ attachmentId: ref.attachmentId as ImageAttachmentRef["attachmentId"],
271
+ mediaType: ref.mediaType as ImageAttachmentRef["mediaType"],
272
+ bytes: ref.bytes,
273
+ width: ref.width,
274
+ height: ref.height,
275
+ ...(ref.name === undefined ? {} : { name: ref.name }),
276
+ };
277
+ }
278
+
279
+ function publicArtifact(meta: MediaArtifactMeta): MediaArtifactMeta {
280
+ return {
281
+ artifactId: meta.artifactId,
282
+ mediaType: meta.mediaType,
283
+ bytes: meta.bytes,
284
+ createdAt: meta.createdAt,
285
+ expiresAt: meta.expiresAt,
286
+ ...(meta.name === undefined ? {} : { name: meta.name }),
287
+ };
288
+ }
289
+
290
+ function publicCodexResult<const Operation extends "generate" | "edit">(
291
+ result: CodexImageResult & { operation: Operation },
292
+ ): {
293
+ operation: Operation;
294
+ model: typeof CODEX_IMAGE_MODEL;
295
+ images: ImageAttachmentRef[];
296
+ references: ImageAttachmentRef[];
297
+ warnings: Array<{ index: number; code: string; message: string }>;
298
+ } {
299
+ return {
300
+ operation: result.operation,
301
+ model: CODEX_IMAGE_MODEL,
302
+ images: result.images.map(publicAttachmentRef),
303
+ references: result.references.map(publicAttachmentRef),
304
+ warnings: result.warnings.map((warning) => ({
305
+ index: warning.index,
306
+ code: warning.code,
307
+ message: warning.message,
308
+ })),
309
+ };
310
+ }
311
+
312
+ function publicImagineImageResult(result: ImagineImageResult): {
313
+ model: string;
314
+ images: Array<{ attachment: ImageAttachmentRef; path: string }>;
315
+ attachment: ImageAttachmentRef;
316
+ path: string;
317
+ } {
318
+ const images = result.images.map((image) => {
319
+ const attachment = publicAttachmentRef(image.attachment);
320
+ return {
321
+ attachment,
322
+ path: publicPluginPath(image.path, imagineImagePath(String(attachment.attachmentId)), IMAGINE_IMAGE_PREFIX),
323
+ };
324
+ });
325
+ const first = images[0];
326
+ const fallback = first ?? {
327
+ attachment: publicAttachmentRef(result.attachment),
328
+ path: publicPluginPath(result.path, imagineImagePath(String(result.attachment.attachmentId)), IMAGINE_IMAGE_PREFIX),
329
+ };
330
+ return {
331
+ model: GROK_IMAGINE_IMAGE_MODEL,
332
+ images,
333
+ attachment: fallback.attachment,
334
+ path: fallback.path,
335
+ };
336
+ }
337
+
338
+ function publicVideoStart(result: ImagineVideoStartResult): ImagineVideoStartResult {
339
+ return {
340
+ model: GROK_IMAGINE_VIDEO_MODEL,
341
+ requestId: result.requestId,
342
+ status: "pending",
343
+ };
344
+ }
345
+
346
+ function publicVideoStatus(result: ImagineVideoStatusResult): {
347
+ readonly requestId: string;
348
+ readonly status: ImagineVideoStatusResult["status"];
349
+ readonly artifact?: MediaArtifactMeta;
350
+ readonly path?: string;
351
+ readonly error?: string;
352
+ } {
353
+ const artifact = result.artifact === undefined ? undefined : publicArtifact(result.artifact);
354
+ const path =
355
+ artifact === undefined
356
+ ? undefined
357
+ : publicPluginPath(result.path, imagineMediaPath(artifact.artifactId), IMAGINE_MEDIA_PREFIX);
358
+ return {
359
+ requestId: result.requestId,
360
+ status: result.status,
361
+ ...(artifact === undefined ? {} : { artifact }),
362
+ ...(path === undefined ? {} : { path }),
363
+ ...(typeof result.error === "string" ? { error: result.error } : {}),
364
+ };
365
+ }
366
+
367
+ function renderImageRefs(
368
+ summary: string,
369
+ refs: readonly {
370
+ attachmentId: string;
371
+ mediaType: string;
372
+ bytes: number;
373
+ width: number;
374
+ height: number;
375
+ name?: string;
376
+ }[],
377
+ ) {
378
+ return [
379
+ { type: "text" as const, text: summary },
380
+ ...refs.map((ref) => ({ type: "image" as const, attachment: publicAttachmentRef(ref) })),
381
+ ];
382
+ }
383
+
384
+ /**
385
+ * Build the five optional capability tools. Callers register the returned
386
+ * definitions; this function has no Cordis / registry side effects. The tools
387
+ * peer is loaded only after Cordis has composed the optional `tools` service.
388
+ */
389
+ export async function createCapabilityTools(options: CapabilityToolsOptions): Promise<readonly ToolDefinition[]> {
390
+ const { defineTool } = await import("@deepseek-ai/dsh-tools");
391
+ const createController: CreateCodexImageController =
392
+ options.createCodexController ??
393
+ ((session) =>
394
+ createCodexImageController({
395
+ auth: options.auth,
396
+ attachments: options.attachments,
397
+ session,
398
+ }));
399
+
400
+ const generate = defineTool({
401
+ name: CODEX_IMAGE_GENERATE_TOOL,
402
+ description:
403
+ "Generate images with the signed-in Codex subscription. Uses the fixed gpt-image-2 model. Does not accept a model id or image URL.",
404
+ parameters: {
405
+ prompt: {
406
+ ...promptParameter,
407
+ description: `Image generation prompt (1-${String(CODEX_IMAGE_PROMPT_MAX_LENGTH)} characters).`,
408
+ },
409
+ n: {
410
+ type: "integer",
411
+ enum: [...IMAGE_COUNT_VALUES],
412
+ description: "How many images to generate. Defaults to the live imageCount setting and cannot exceed it.",
413
+ },
414
+ size: { type: "string", enum: [...CODEX_IMAGE_SIZES], description: "Output size. Defaults to auto." },
415
+ quality: { type: "string", enum: [...CODEX_IMAGE_QUALITIES], description: "Output quality. Defaults to auto." },
416
+ background: {
417
+ type: "string",
418
+ enum: [...CODEX_IMAGE_BACKGROUNDS],
419
+ description: "Background mode. Defaults to auto.",
420
+ },
421
+ },
422
+ output: {
423
+ schema: {
424
+ type: "object",
425
+ additionalProperties: false,
426
+ properties: {
427
+ operation: { type: "string", required: true, enum: ["generate"] },
428
+ model: { type: "string", required: true },
429
+ images: { type: "array", required: true, items: attachmentRefSchema },
430
+ references: { type: "array", required: true, items: attachmentRefSchema },
431
+ warnings: { type: "array", required: true, items: warningSchema },
432
+ },
433
+ },
434
+ render: (_args, value) =>
435
+ renderImageRefs(`Generated ${String(value.images.length)} Codex image(s) with ${value.model}.`, value.images),
436
+ },
437
+ async execute(args, exec) {
438
+ const settings = options.current();
439
+ if (!settings.codexImages) disabled(CODEX_IMAGE_GENERATE_TOOL);
440
+ const n = resolveImageCount(args.n, settings);
441
+ const result = await createController(await generateSession(exec, options.resolveCodexImageRoute)).generate(
442
+ {
443
+ prompt: args.prompt,
444
+ n,
445
+ ...(args.size === undefined ? {} : { size: args.size as CodexImageSize }),
446
+ ...(args.quality === undefined ? {} : { quality: args.quality as CodexImageQuality }),
447
+ ...(args.background === undefined ? {} : { background: args.background as CodexImageBackground }),
448
+ },
449
+ exec.signal,
450
+ );
451
+ return publicCodexResult({ ...result, operation: "generate" });
452
+ },
453
+ });
454
+
455
+ const edit = defineTool({
456
+ name: CODEX_IMAGE_EDIT_TOOL,
457
+ description:
458
+ "Edit current-session Codex images. imageIds must be canonical attachment ids visible in this session. Does not accept HTTP(S) URLs or a model id.",
459
+ parameters: {
460
+ prompt: {
461
+ ...promptParameter,
462
+ description: `Edit instructions (1-${String(CODEX_IMAGE_PROMPT_MAX_LENGTH)} characters).`,
463
+ },
464
+ imageIds: {
465
+ type: "array",
466
+ required: true,
467
+ items: { type: "string" },
468
+ description: "One to five current-session image attachment ids (optionally image:<id>). URLs are rejected.",
469
+ },
470
+ n: {
471
+ type: "integer",
472
+ enum: [...IMAGE_COUNT_VALUES],
473
+ description: "How many edited images to return. Defaults to the live imageCount setting and cannot exceed it.",
474
+ },
475
+ size: { type: "string", enum: [...CODEX_IMAGE_SIZES], description: "Output size. Defaults to auto." },
476
+ quality: { type: "string", enum: [...CODEX_IMAGE_QUALITIES], description: "Output quality. Defaults to auto." },
477
+ background: {
478
+ type: "string",
479
+ enum: [...CODEX_IMAGE_BACKGROUNDS],
480
+ description: "Background mode. Defaults to auto.",
481
+ },
482
+ },
483
+ output: {
484
+ schema: {
485
+ type: "object",
486
+ additionalProperties: false,
487
+ properties: {
488
+ operation: { type: "string", required: true, enum: ["edit"] },
489
+ model: { type: "string", required: true },
490
+ images: { type: "array", required: true, items: attachmentRefSchema },
491
+ references: { type: "array", required: true, items: attachmentRefSchema },
492
+ warnings: { type: "array", required: true, items: warningSchema },
493
+ },
494
+ },
495
+ render: (_args, value) =>
496
+ renderImageRefs(`Edited ${String(value.images.length)} Codex image(s) with ${value.model}.`, value.images),
497
+ },
498
+ async execute(args, exec) {
499
+ const settings = options.current();
500
+ if (!settings.codexImageEdits || !settings.codexImages) disabled(CODEX_IMAGE_EDIT_TOOL);
501
+ const n = resolveImageCount(args.n, settings);
502
+ const result = await createController(await requireEditSession(exec, options.resolveCodexImageRoute)).edit(
503
+ {
504
+ prompt: args.prompt,
505
+ imageIds: args.imageIds,
506
+ n,
507
+ ...(args.size === undefined ? {} : { size: args.size as CodexImageSize }),
508
+ ...(args.quality === undefined ? {} : { quality: args.quality as CodexImageQuality }),
509
+ ...(args.background === undefined ? {} : { background: args.background as CodexImageBackground }),
510
+ },
511
+ exec.signal,
512
+ );
513
+ return publicCodexResult({ ...result, operation: "edit" });
514
+ },
515
+ });
516
+
517
+ const imagineImage = defineTool({
518
+ name: GROK_IMAGINE_IMAGE_TOOL,
519
+ description:
520
+ "Generate images with official xAI Imagine. Uses the fixed grok-imagine-image-2.0 model. Does not accept a model id or source URL.",
521
+ parameters: {
522
+ prompt: {
523
+ ...promptParameter,
524
+ description: `Image generation prompt (1-${String(IMAGINE_PROMPT_MAX_LENGTH)} characters).`,
525
+ },
526
+ n: {
527
+ type: "integer",
528
+ enum: [...IMAGE_COUNT_VALUES],
529
+ description: "How many images to generate. Defaults to the live imageCount setting and cannot exceed it.",
530
+ },
531
+ aspectRatio: {
532
+ type: "string",
533
+ enum: [...IMAGINE_IMAGE_ASPECT_RATIOS],
534
+ description: "Optional Imagine image aspect ratio.",
535
+ },
536
+ resolution: {
537
+ type: "string",
538
+ enum: [...IMAGINE_IMAGE_RESOLUTIONS],
539
+ description: "Optional Imagine image resolution.",
540
+ },
541
+ },
542
+ output: {
543
+ schema: {
544
+ type: "object",
545
+ additionalProperties: false,
546
+ properties: {
547
+ model: { type: "string", required: true },
548
+ images: { type: "array", required: true, items: imagineImageItemSchema },
549
+ attachment: { ...attachmentRefSchema, required: true },
550
+ path: { type: "string", required: true },
551
+ },
552
+ },
553
+ render: (_args, value) =>
554
+ renderImageRefs(
555
+ `Generated ${String(value.images.length)} Imagine image(s).`,
556
+ value.images.map((image) => image.attachment),
557
+ ),
558
+ },
559
+ async execute(args, exec) {
560
+ const settings = options.current();
561
+ if (!settings.grokImagineImage) disabled(GROK_IMAGINE_IMAGE_TOOL);
562
+ const input: GenerateImagineImageInput = {
563
+ prompt: args.prompt,
564
+ n: resolveImageCount(args.n, settings),
565
+ ...(args.aspectRatio === undefined ? {} : { aspectRatio: args.aspectRatio as ImagineImageAspectRatio }),
566
+ ...(args.resolution === undefined ? {} : { resolution: args.resolution as ImagineImageResolution }),
567
+ };
568
+ return publicImagineImageResult(await options.imagine.generateImage(input, exec.signal));
569
+ },
570
+ });
571
+
572
+ const imagineVideo = defineTool({
573
+ name: GROK_IMAGINE_VIDEO_TOOL,
574
+ description:
575
+ "Start an official xAI Imagine video job. Uses the fixed grok-imagine-video-1.5 model. Poll status with grok_imagine_video_status and the returned requestId.",
576
+ parameters: {
577
+ prompt: {
578
+ ...promptParameter,
579
+ description: `Video generation prompt (1-${String(IMAGINE_PROMPT_MAX_LENGTH)} characters).`,
580
+ },
581
+ duration: {
582
+ type: "integer",
583
+ enum: [...VIDEO_DURATION_VALUES],
584
+ description: `Optional duration in seconds (${String(IMAGINE_VIDEO_MIN_DURATION_SECONDS)}-${String(IMAGINE_VIDEO_MAX_DURATION_SECONDS)}).`,
585
+ },
586
+ aspectRatio: {
587
+ type: "string",
588
+ enum: [...IMAGINE_VIDEO_ASPECT_RATIOS],
589
+ description: "Optional Imagine video aspect ratio.",
590
+ },
591
+ resolution: {
592
+ type: "string",
593
+ enum: [...IMAGINE_VIDEO_RESOLUTIONS],
594
+ description: "Optional Imagine video resolution.",
595
+ },
596
+ },
597
+ output: {
598
+ schema: {
599
+ type: "object",
600
+ additionalProperties: false,
601
+ properties: {
602
+ model: { type: "string", required: true },
603
+ requestId: { ...videoRequestIdSchema },
604
+ status: { type: "string", required: true, enum: ["pending"] },
605
+ },
606
+ },
607
+ render: (_args, value) => [{ type: "text", text: `Imagine video job ${value.requestId} is ${value.status}.` }],
608
+ },
609
+ async execute(args, exec) {
610
+ const settings = options.current();
611
+ if (!settings.grokImagineVideo) disabled(GROK_IMAGINE_VIDEO_TOOL);
612
+ const input: StartImagineVideoInput = {
613
+ prompt: args.prompt,
614
+ ...(args.duration === undefined ? {} : { duration: args.duration }),
615
+ ...(args.aspectRatio === undefined ? {} : { aspectRatio: args.aspectRatio as ImagineVideoAspectRatio }),
616
+ ...(args.resolution === undefined ? {} : { resolution: args.resolution as ImagineVideoResolution }),
617
+ };
618
+ return publicVideoStart(await options.imagine.startVideo(input, exec.signal));
619
+ },
620
+ });
621
+
622
+ const imagineVideoStatus = defineTool({
623
+ name: GROK_IMAGINE_VIDEO_STATUS_TOOL,
624
+ description:
625
+ "Poll a previously started Imagine video job by requestId. Completed results expose only an opaque artifact id and same-origin path.",
626
+ parameters: {
627
+ requestId: { ...videoRequestIdSchema, description: "Opaque request id returned by grok_imagine_video." },
628
+ },
629
+ output: {
630
+ schema: {
631
+ type: "object",
632
+ additionalProperties: false,
633
+ properties: {
634
+ requestId: { ...videoRequestIdSchema },
635
+ status: { type: "string", required: true, enum: ["pending", "completed", "failed"] },
636
+ artifact: artifactSchema,
637
+ path: { type: "string" },
638
+ error: { type: "string" },
639
+ },
640
+ },
641
+ render: (_args, value) => {
642
+ const detail =
643
+ value.status === "completed" && value.path !== undefined
644
+ ? `Imagine video ${value.requestId} completed (${value.path}).`
645
+ : `Imagine video ${value.requestId} is ${value.status}.`;
646
+ return [{ type: "text", text: detail }];
647
+ },
648
+ },
649
+ async execute(args, exec) {
650
+ const settings = options.current();
651
+ if (!settings.grokImagineVideo) disabled(GROK_IMAGINE_VIDEO_STATUS_TOOL);
652
+ const requestId = parseVideoRequestId(args.requestId);
653
+ return publicVideoStatus(await options.imagine.videoStatus(requestId, { signal: exec.signal }));
654
+ },
655
+ });
656
+
657
+ return [generate, edit, imagineImage, imagineVideo, imagineVideoStatus];
658
+ }
659
+
660
+ export const CAPABILITY_TOOL_NAMES = [
661
+ CODEX_IMAGE_GENERATE_TOOL,
662
+ CODEX_IMAGE_EDIT_TOOL,
663
+ GROK_IMAGINE_IMAGE_TOOL,
664
+ GROK_IMAGINE_VIDEO_TOOL,
665
+ GROK_IMAGINE_VIDEO_STATUS_TOOL,
666
+ ] as const;