grok-cli-to-openai-compatible 1.7.1 → 1.7.3

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/README-ZH.md CHANGED
@@ -36,7 +36,7 @@
36
36
  |------|:----:|:---------:|:------------------:|------------|
37
37
  | 文字 + stream | ✅ | ✅ | ✅ | 協議開關 |
38
38
  | Vision / 圖片 | ✅ | ✅ | ✅ | `vision` |
39
- | Images `/v1/images/generations` + `/edits` | ✅ | — | — | `imagesApi` + agent key |
39
+ | Images `/v1/images/generations` + `/edits` | ✅ OpenAI 形狀(`b64_json` / `url`) | — | — | `imagesApi` + `tools` + agent key |
40
40
  | Files `/v1/files` | ✅ | — | — | `filesOpenAiAlias` |
41
41
  | Videos `/v1/videos` | ✅ Grok `image_to_video`(1–15 秒)/`reference_to_video` + voices | — | — | `videoApi` |
42
42
  | Audio speech / transcriptions | ✅ mock 或 503 | — | — | `audioApi` + provider env |
@@ -314,7 +314,7 @@ gctoac logs clear
314
314
  | 加密 | AES-256-GCM 加密 prompt、response、檔案 |
315
315
  | 對話歷史 | 多輪對話、上下文模式(full / summary / recent) |
316
316
  | Admin Panel | OTP session 登入;**分 tab** 頁面(佇列/媒體/系統/PM2/DDoS/API 能力);精簡 EN/繁中文案 |
317
- | 媒體庫 | 工作室(生成/編輯/**1–15 秒影片**/配音)、資產與工作、多格式預覽 lightbox(`blob:` CSP 安全) |
317
+ | 媒體庫 | 工作室(生成/編輯/**1–15 秒影片**/配音)、資產與工作、多格式預覽 lightbox(`blob:` CSP 安全)。沙箱沒有 `output.png` 時,會從今次 Imagine session 的 `images/` 收圖 |
318
318
  | 對話佇列 | 持久化 SQLite 工作、公平輪詢、暫停/排空、死信、Idempotency-Key、`QUEUE_BACKEND`、離線串流 fallback |
319
319
  | DDoS 中心 | 即時連線、黑名單、自動封鎖、可配置策略與預設檔(分 tab) |
320
320
  | 反向代理 | 信任層數 + CF / nginx / X-Forwarded-For 真實 IP |
@@ -368,6 +368,61 @@ const res = await client.chat.completions.create({
368
368
  console.log(res.choices[0].message.content);
369
369
  ```
370
370
 
371
+ ### Images(OpenAI 形狀)
372
+
373
+ `POST /v1/images/generations` 與 `POST /v1/images/edits` 回傳官方 Images 物件。需要 **`imagesApi` + `tools`**,以及 **agent**(或管理員)金鑰;safe 金鑰會得 **403**。
374
+
375
+ ```bash
376
+ curl -s http://127.0.0.1:3847/v1/images/generations \
377
+ -H "Authorization: Bearer $API_KEY" \
378
+ -H "Content-Type: application/json" \
379
+ -d '{
380
+ "prompt": "紅色方塊",
381
+ "n": 1,
382
+ "aspect_ratio": "16:9",
383
+ "response_format": "b64_json"
384
+ }'
385
+ ```
386
+
387
+ ```json
388
+ {
389
+ "created": 1710000000,
390
+ "data": [{ "b64_json": "iVBOR…" }],
391
+ "grok": { "provider": "grok-tools", "asset_ids": ["uuid"] }
392
+ }
393
+ ```
394
+
395
+ | 欄位/主題 | 行為 |
396
+ |------------|------|
397
+ | `response_format` | `b64_json`(預設,與 GPT image 模型相同)或 `url` |
398
+ | `size` | OpenAI 像素(`1024x1024` 等)**或** Grok 比例(`16:9`) |
399
+ | `aspect_ratio` | Grok Imagine(`1:1`、`16:9` 等)——優先於像素尺寸 |
400
+ | `n` | 1–4;Grok 無批次生成,Gateway 或會循環。`n=1` 只交一張 |
401
+ | `data[].url` | Gateway 本機 `/v1/media/assets/:id/content`(同一把 API 金鑰),不是 OpenAI CDN |
402
+ | `grok` | Gateway 擴充(`provider`、`asset_ids`)。OpenAI SDK 會忽略未知欄位 |
403
+
404
+ **生成如何真正收到檔案(v1.7.3+):** Grok Imagine 永遠寫到
405
+ `~/.grok/sessions/<encodeURIComponent(sandbox-cwd)>/<session-uuid>/images/`
406
+ (例如 `1.jpg`)。media-run 沙箱**沒有 bash**,agent 無法自行 `cp` 到 `output.png`。Gateway 只把**今次 run 對應 session** 最新一張圖,在 `image_gen` 成功時(以及 Grok 結束後)複製到沙箱 `output.jpg|png|webp`。
407
+
408
+ 收圖順序:
409
+
410
+ 1. 沙箱根目錄 `output.png` / `output.jpg` / `output.webp` / `output.jpeg`
411
+ 2. 遞歸掃描沙箱(realpath;不會跟隨 symlink 離開今次 run)
412
+ 3. 只收今次 run 的 Grok session `images/`——**不會**掃描整個 `~/.grok/sessions`
413
+
414
+ 優先最新一張。忽略 `input.png`、`mask.png`、`frame.png`、`ref-*.png`。只有完全找不到圖像時才會回 **502** `no_image_in_sandbox`——**不是** `imagesApi` 或 API 金鑰問題。編輯使用 `--tools image_edit` + `input.png`。影片收檔同樣可取今次 session 的 `videos/`/`images/` 以及沙箱 `output.mp4`。
415
+
416
+ ```ts
417
+ const img = await client.images.generate({
418
+ model: 'grok-4.6',
419
+ prompt: '紅色方塊',
420
+ n: 1,
421
+ response_format: 'b64_json',
422
+ });
423
+ const b64 = img.data[0].b64_json;
424
+ ```
425
+
371
426
  ### Chat body 擴充欄位
372
427
 
373
428
  ```json
@@ -452,6 +507,10 @@ POST /admin/api/queue/purge-dead
452
507
  | POST | `/v1/chat/completions` | OpenAI Chat(佇列;可選 `Idempotency-Key`) |
453
508
  | POST | `/v1/responses` | OpenAI Responses 文字子集 |
454
509
  | POST | `/v1/messages` | Anthropic Messages(Bearer 或 `x-api-key`) |
510
+ | POST | `/v1/images/generations` | OpenAI Images 生成(`b64_json`/`url`;沙箱為空時會收今次 session `images/`) |
511
+ | POST | `/v1/images/edits` | OpenAI Images 編輯(multipart `image` + 可選 `mask`) |
512
+ | GET | `/v1/media/assets/:id` | 已存資產中繼資料 |
513
+ | GET | `/v1/media/assets/:id/content` | 已存資產內容(同一把 API 金鑰) |
455
514
  | POST | `/v1/videos` | Grok 影片 job(`seconds` 1–15;`voices[]` → `reference_to_video`) |
456
515
  | GET | `/v1/videos/:id` | 查影片 job |
457
516
  | GET | `/admin/api/grok/inspect` | 本機 `grok inspect --json` 快照 |
package/README.md CHANGED
@@ -36,7 +36,7 @@ Production OpenAI-compatible HTTP gateway for local **[Grok CLI](https://x.ai)**
36
36
  |------------|:----:|:---------:|:------------------:|--------------|
37
37
  | Text + stream | ✅ | ✅ | ✅ | protocol flags |
38
38
  | Vision / image parts | ✅ | ✅ | ✅ | `vision` |
39
- | **Images** `/v1/images/generations` + `/edits` | ✅ OpenAI-shaped | — | — | `imagesApi` + agent key |
39
+ | **Images** `/v1/images/generations` + `/edits` | ✅ OpenAI-shaped (`b64_json` / `url`) | — | — | `imagesApi` + `tools` + agent key |
40
40
  | **Files** `/v1/files` (docs alias) | ✅ | — | — | `filesOpenAiAlias` |
41
41
  | **Videos** `/v1/videos` | ✅ Grok `image_to_video` (1–15s) / `reference_to_video` + voices | — | — | `videoApi` |
42
42
  | **Audio** `/v1/audio/speech` + `/transcriptions` | ✅ mock or 503 | — | — | `audioApi` + provider env |
@@ -316,7 +316,7 @@ gctoac logs clear
316
316
  | Encryption | AES-256-GCM for prompts, responses, files |
317
317
  | Chat history | Multi-turn conversations, context modes (full / summary / recent) |
318
318
  | Admin Panel | OTP session login; **tabbed** pages (queue / media / system / PM2 / DDoS / API features); compact EN/ZH copy |
319
- | Media library | Studio (gen/edit/**1–15s video**/voices), assets & jobs, multi-format preview lightbox (`blob:` CSP-safe) |
319
+ | Media library | Studio (gen/edit/**1–15s video**/voices), assets & jobs, multi-format preview lightbox (`blob:` CSP-safe). Generate harvests Imagine session `images/` when sandbox `output.png` is missing |
320
320
  | Chat queue | Durable SQLite jobs, fair RR, pause/drain, DLQ, Idempotency-Key, `QUEUE_BACKEND`, offline stream fallback |
321
321
  | DDoS center | Live connections, blacklist, auto-ban rules, presets, runtime policy (tabbed) |
322
322
  | Reverse proxy | Trust hops + CF / nginx / X-Forwarded-For client IP |
@@ -393,6 +393,61 @@ const r = await client.responses.create({
393
393
 
394
394
  Supported: `input` string or message items (text), `instructions`, `stream`, `tools` (→ Grok tools when enabled), `previous_response_id` (context chain), `GET/DELETE /v1/responses/:id` (encrypted store).
395
395
 
396
+ ### Images (OpenAI-shaped)
397
+
398
+ `POST /v1/images/generations` and `POST /v1/images/edits` return the official Images object. Requires **`imagesApi` + `tools`** and an **agent** (or admin) key — safe keys get **403**.
399
+
400
+ ```bash
401
+ curl -s http://127.0.0.1:3847/v1/images/generations \
402
+ -H "Authorization: Bearer $API_KEY" \
403
+ -H "Content-Type: application/json" \
404
+ -d '{
405
+ "prompt": "a red square",
406
+ "n": 1,
407
+ "aspect_ratio": "16:9",
408
+ "response_format": "b64_json"
409
+ }'
410
+ ```
411
+
412
+ ```json
413
+ {
414
+ "created": 1710000000,
415
+ "data": [{ "b64_json": "iVBOR…" }],
416
+ "grok": { "provider": "grok-tools", "asset_ids": ["uuid"] }
417
+ }
418
+ ```
419
+
420
+ | Field / topic | Behavior |
421
+ |---------------|----------|
422
+ | `response_format` | `b64_json` (default, same as GPT image models) or `url` |
423
+ | `size` | OpenAI pixels (`1024x1024`, …) **or** a Grok aspect (`16:9`) |
424
+ | `aspect_ratio` | Grok Imagine (`1:1`, `16:9`, …) — preferred over pixel sizes |
425
+ | `n` | 1–4; Grok has no batch generate — gateway may loop. `n=1` returns one image |
426
+ | `data[].url` | Gateway-local `/v1/media/assets/:id/content` (same API key). Not an OpenAI CDN |
427
+ | `grok` | Gateway extension (`provider`, `asset_ids`). OpenAI SDKs ignore unknown fields |
428
+
429
+ **How generate actually gets a file (v1.7.3+):** Grok Imagine always writes to
430
+ `~/.grok/sessions/<encodeURIComponent(sandbox-cwd)>/<session-uuid>/images/`
431
+ (e.g. `1.jpg`). The media-run sandbox has **no bash**, so the agent cannot `cp` that file to `output.png`. The gateway copies the newest image for **this run’s session only** into sandbox `output.jpg|png|webp` as soon as `image_gen` succeeds (and again when Grok exits).
432
+
433
+ Collect order:
434
+
435
+ 1. Sandbox-root `output.png` / `output.jpg` / `output.webp` / `output.jpeg`
436
+ 2. Recursive scan of the sandbox (realpath; never follows links out of the run dir)
437
+ 3. This run’s Grok session `images/` only — never the rest of `~/.grok/sessions`
438
+
439
+ Prefer the newest file. Ignore `input.png`, `mask.png`, `frame.png`, `ref-*.png`. **502** `no_image_in_sandbox` only if nothing exists — that is **not** an `imagesApi` or API-key failure. Edit uses `--tools image_edit` + `input.png`. Video collect can also take this run’s session `videos/` / `images/` plus sandbox `output.mp4`.
440
+
441
+ ```ts
442
+ const img = await client.images.generate({
443
+ model: 'grok-4.6',
444
+ prompt: 'a red square',
445
+ n: 1,
446
+ response_format: 'b64_json',
447
+ });
448
+ const b64 = img.data[0].b64_json;
449
+ ```
450
+
396
451
  ### Anthropic Messages
397
452
 
398
453
  ```bash
@@ -513,6 +568,10 @@ POST /admin/api/queue/purge-dead
513
568
  | POST | `/v1/responses` | OpenAI Responses (text + store) |
514
569
  | GET/DELETE | `/v1/responses/:id` | Retrieve / soft-delete stored response |
515
570
  | POST | `/v1/messages` | Anthropic Messages (Bearer or `x-api-key`) |
571
+ | POST | `/v1/images/generations` | OpenAI Images generate (`b64_json` / `url`; harvests session `images/` if sandbox is empty) |
572
+ | POST | `/v1/images/edits` | OpenAI Images edit (multipart `image` + optional `mask`) |
573
+ | GET | `/v1/media/assets/:id` | Stored asset metadata |
574
+ | GET | `/v1/media/assets/:id/content` | Stored asset bytes (same API key) |
516
575
  | POST | `/v1/videos` | Grok video job (`seconds` 1–15; `voices[]` → `reference_to_video`) |
517
576
  | GET | `/v1/videos/:id` | Poll video job |
518
577
  | GET | `/admin/api/grok/inspect` | Local `grok inspect --json` snapshot |
@@ -41,5 +41,34 @@ export declare class GrokToolsMediaProvider implements MediaProvider {
41
41
  private runGrokCollectImages;
42
42
  private runGrokCollectMedia;
43
43
  }
44
+ export declare function isIgnoredMediaName(name: string): boolean;
45
+ export declare function eventSessionId(ev: Record<string, unknown>): string | null;
46
+ export declare function isSuccessfulMediaToolEvent(ev: Record<string, unknown>): boolean;
47
+ /** ~/.grok/sessions/<encodeURIComponent(realpath(sandbox))> */
48
+ export declare function grokSessionGroupDir(sandboxAbs: string, grokHome?: string): string;
49
+ export declare function findRunSessionDir(sandboxAbs: string, sessionId?: string | null, grokHome?: string): Promise<string | null>;
50
+ export declare function listRunSessionMedia(opts: {
51
+ sandbox: string;
52
+ sessionId?: string | null;
53
+ collect: 'image' | 'video';
54
+ grokHome?: string;
55
+ }): Promise<Set<string>>;
56
+ export declare function copyNewestSessionMediaToSandbox(opts: {
57
+ sandbox: string;
58
+ sessionId?: string | null;
59
+ collect: 'image' | 'video';
60
+ grokHome?: string;
61
+ overwrite?: boolean;
62
+ }): Promise<string | null>;
63
+ /** Recursive sandbox scan. Never follows links out of `dir` (not ~/.grok/sessions). */
64
+ export declare function listMediaFiles(dir: string, exts: Set<string>): Promise<Set<string>>;
65
+ /** Prefer root output.*, then files new this run, then newest mtime in sandbox. */
66
+ export declare function selectCollectedMedia(opts: {
67
+ sandbox: string;
68
+ before: Set<string>;
69
+ after: Set<string>;
70
+ collect: 'image' | 'video';
71
+ n?: number;
72
+ }): Promise<string[]>;
44
73
  export declare const grokToolsMediaProvider: GrokToolsMediaProvider;
45
74
  //# sourceMappingURL=grok-tools.provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grok-tools.provider.d.ts","sourceRoot":"","sources":["../../../../src/services/media/providers/grok-tools.provider.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,aAAa,EACd,MAAM,4BAA4B,CAAC;AAyBpC;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,aAAa;IAC1D,QAAQ,CAAC,EAAE,gBAAgB;IAErB,SAAS,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAwB1D,aAAa,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAoBnE;;OAEG;IACG,sBAAsB,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAChC,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IA2B5B;;OAEG;IACG,2BAA2B,CAAC,GAAG,EAAE;QACrC,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAChC,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;YAsCd,oBAAoB;YAUpB,mBAAmB;CAuHlC;AA6BD,eAAO,MAAM,sBAAsB,wBAA+B,CAAC"}
1
+ {"version":3,"file":"grok-tools.provider.d.ts","sourceRoot":"","sources":["../../../../src/services/media/providers/grok-tools.provider.ts"],"names":[],"mappings":"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"}