@comfyorg/sdk 0.1.0 → 0.1.1

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.md CHANGED
@@ -79,6 +79,23 @@ await job.wait(); // poll to terminal (adaptive backoff); or call job.refresh()
79
79
  console.log(job.status, job.outputs);
80
80
  ```
81
81
 
82
+ ## Partner (API) node auth
83
+
84
+ Workflows that use partner/API nodes (Gemini, etc.) need a Comfy API key to
85
+ authenticate them. Pass it per submit with `apiKey`. This is **not** the same as
86
+ the credential you construct `Comfy` with: the constructor key authenticates
87
+ _you_ to the server, while this one authenticates the partner nodes _inside_ the
88
+ workflow (it is often the same `comfyui-…` key):
89
+
90
+ ```ts
91
+ const job = await client.run(wf, { apiKey: "comfyui-…" });
92
+ // or: await client.submit(wf, { apiKey: "comfyui-…" });
93
+ ```
94
+
95
+ The SDK sends it once as `extra_data.api_key_comfy_org` alongside the workflow —
96
+ one key authenticates every partner node in the graph. It is never logged or
97
+ persisted by the SDK. Omit `apiKey` and no `extra_data` is sent at all.
98
+
82
99
  ## Assets and `core/ASSET`
83
100
 
84
101
  `client.assets.fromFile(path)` / `client.assets.fromBytes(data, options)`
@@ -371,6 +371,15 @@ export type PostJobsData = {
371
371
  workflow: {
372
372
  [key: string]: unknown;
373
373
  };
374
+ /**
375
+ * Optional typed extras submitted alongside the workflow. Closed: only the enumerated properties are accepted.
376
+ */
377
+ extra_data?: {
378
+ /**
379
+ * API key for partner (API) nodes.
380
+ */
381
+ api_key_comfy_org?: string;
382
+ };
374
383
  };
375
384
  headers?: {
376
385
  /**
@@ -253,6 +253,9 @@ export declare const zGetAssetContentPath: z.ZodObject<{
253
253
  export declare const zGetAssetContentResponse: z.ZodString;
254
254
  export declare const zPostJobsBody: z.ZodObject<{
255
255
  workflow: z.ZodRecord<z.ZodString, z.ZodUnknown>;
256
+ extra_data: z.ZodOptional<z.ZodObject<{
257
+ api_key_comfy_org: z.ZodOptional<z.ZodString>;
258
+ }, z.core.$strip>>;
256
259
  }, z.core.$strip>;
257
260
  export declare const zPostJobsHeaders: z.ZodObject<{
258
261
  'Idempotency-Key': z.ZodOptional<z.ZodString>;
@@ -172,7 +172,10 @@ export const zGetAssetContentPath = z.object({
172
172
  */
173
173
  export const zGetAssetContentResponse = z.string();
174
174
  export const zPostJobsBody = z.object({
175
- workflow: z.record(z.string(), z.unknown())
175
+ workflow: z.record(z.string(), z.unknown()),
176
+ extra_data: z.object({
177
+ api_key_comfy_org: z.string().optional()
178
+ }).optional()
176
179
  });
177
180
  export const zPostJobsHeaders = z.object({
178
181
  'Idempotency-Key': z.string().optional()
@@ -25,7 +25,7 @@
25
25
  * the Python SDK (its `AsyncComfyLow`; there is no sync variant here — see
26
26
  * the package README for why).
27
27
  */
28
- import type { Asset, Job } from "./generated/types.gen.js";
28
+ import type { Asset, Job, PostJobsData } from "./generated/types.gen.js";
29
29
  import { type RawEvent } from "./sse.js";
30
30
  export interface RequestOptions {
31
31
  headers?: Record<string, string>;
@@ -100,9 +100,14 @@ export declare class ComfyLow {
100
100
  range?: readonly [number, number];
101
101
  signal?: AbortSignal;
102
102
  }): Promise<Response>;
103
- /** `POST /api/v2/jobs`. */
103
+ /**
104
+ * `POST /api/v2/jobs`. `extraData` (e.g. the partner-node API key) is a
105
+ * sibling of `workflow` on the wire, per the spec's closed `extra_data`
106
+ * object; omitted entirely when not provided, never sent empty.
107
+ */
104
108
  postJobs(workflow: Record<string, unknown>, options?: {
105
109
  idempotencyKey?: string;
110
+ extraData?: PostJobsData["body"]["extra_data"];
106
111
  signal?: AbortSignal;
107
112
  }): Promise<Job>;
108
113
  /** `GET /api/v2/jobs/{id}` (or an absolute self link). */
@@ -210,13 +210,22 @@ export class ComfyLow {
210
210
  return response;
211
211
  }
212
212
  // -- jobs -----------------------------------------------------------------
213
- /** `POST /api/v2/jobs`. */
213
+ /**
214
+ * `POST /api/v2/jobs`. `extraData` (e.g. the partner-node API key) is a
215
+ * sibling of `workflow` on the wire, per the spec's closed `extra_data`
216
+ * object; omitted entirely when not provided, never sent empty.
217
+ */
214
218
  async postJobs(workflow, options = {}) {
215
219
  const headers = {};
216
220
  if (options.idempotencyKey) {
217
221
  headers["Idempotency-Key"] = options.idempotencyKey;
218
222
  }
219
223
  const json = { workflow };
224
+ // Only attach a non-empty extra_data — never serialize an empty object onto
225
+ // the wire (mirrors the Python low layer's truthy guard).
226
+ if (options.extraData && Object.keys(options.extraData).length > 0) {
227
+ json.extra_data = options.extraData;
228
+ }
220
229
  const response = await this.request("POST", "/jobs", { headers, json, signal: options.signal });
221
230
  return this.parseOrRaise(response, [201]);
222
231
  }
@@ -57,14 +57,22 @@ export declare class Comfy {
57
57
  * idempotent, pass an explicit `idempotencyKey` and reuse it. Note a reused
58
58
  * key is *rejected*, not replayed: on reuse, catch the error and poll/list
59
59
  * for the job the first attempt already created.
60
+ *
61
+ * Pass `apiKey` to authenticate partner (API) nodes in the workflow (for
62
+ * example Gemini) — it is sent once, as `extra_data.api_key_comfy_org`
63
+ * alongside the workflow, and is unrelated to the `Idempotency-Key`: it
64
+ * does not affect idempotency and is never persisted or logged by this
65
+ * SDK. Omit it and no `extra_data` is sent at all.
60
66
  */
61
67
  submit(workflow: Workflow, options?: {
62
68
  idempotencyKey?: string;
69
+ apiKey?: string;
63
70
  signal?: AbortSignal;
64
71
  }): Promise<Job>;
65
72
  /** Submit, then poll to terminal (authoritative). Throws on failure. */
66
73
  run(workflow: Workflow, options?: {
67
74
  timeoutMs?: number;
75
+ apiKey?: string;
68
76
  signal?: AbortSignal;
69
77
  }): Promise<Job>;
70
78
  }
@@ -77,16 +77,26 @@ export class Comfy {
77
77
  * idempotent, pass an explicit `idempotencyKey` and reuse it. Note a reused
78
78
  * key is *rejected*, not replayed: on reuse, catch the error and poll/list
79
79
  * for the job the first attempt already created.
80
+ *
81
+ * Pass `apiKey` to authenticate partner (API) nodes in the workflow (for
82
+ * example Gemini) — it is sent once, as `extra_data.api_key_comfy_org`
83
+ * alongside the workflow, and is unrelated to the `Idempotency-Key`: it
84
+ * does not affect idempotency and is never persisted or logged by this
85
+ * SDK. Omit it and no `extra_data` is sent at all.
80
86
  */
81
87
  async submit(workflow, options = {}) {
82
88
  guardUiFormat(workflow);
83
89
  const graph = await this.materialize(workflow, options.signal);
84
90
  const key = options.idempotencyKey ?? newIdempotencyKey();
91
+ // A falsy apiKey (undefined or "") means "no key" — send no extra_data,
92
+ // matching the Python SDK's behavior so the two stay in lockstep.
93
+ const extraData = options.apiKey ? { api_key_comfy_org: options.apiKey } : undefined;
85
94
  const deadline = Date.now() + QUEUE_RETRY_BUDGET_MS;
86
95
  for (;;) {
87
96
  try {
88
97
  const job = await this.low.postJobs(graph, {
89
98
  idempotencyKey: key,
99
+ extraData,
90
100
  signal: options.signal,
91
101
  });
92
102
  return new Job(this.low, job);
@@ -105,7 +115,7 @@ export class Comfy {
105
115
  }
106
116
  /** Submit, then poll to terminal (authoritative). Throws on failure. */
107
117
  async run(workflow, options = {}) {
108
- const job = await this.submit(workflow, { signal: options.signal });
118
+ const job = await this.submit(workflow, { apiKey: options.apiKey, signal: options.signal });
109
119
  return options.timeoutMs === undefined
110
120
  ? job.result(options.signal)
111
121
  : runWithTimeout(job, options.timeoutMs, options.signal);
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "@comfyorg/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "description": "TypeScript SDK for running ComfyUI workflows via the Comfy API v2 (self-hosted, Comfy Cloud, serverless).",
6
+ "homepage": "https://github.com/Comfy-Org/ComfyTypeScriptSDK#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/Comfy-Org/ComfyTypeScriptSDK/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/Comfy-Org/ComfyTypeScriptSDK.git"
13
+ },
6
14
  "files": [
7
15
  "dist"
8
16
  ],
@@ -19,17 +27,6 @@
19
27
  "default": "./dist/low/index.js"
20
28
  }
21
29
  },
22
- "scripts": {
23
- "build": "tsc",
24
- "generate": "openapi-ts",
25
- "lint": "oxlint .",
26
- "format": "oxfmt --write .",
27
- "format:check": "oxfmt --check .",
28
- "typecheck": "tsc --noEmit",
29
- "test": "vitest run",
30
- "test:coverage": "vitest run --coverage",
31
- "check:spec-drift": "node scripts/check-spec-drift.mjs"
32
- },
33
30
  "dependencies": {
34
31
  "eventsource-parser": "^3.0.0",
35
32
  "hash-wasm": "^4.11.0",
@@ -48,5 +45,15 @@
48
45
  "engines": {
49
46
  "node": ">=22"
50
47
  },
51
- "packageManager": "pnpm@11.13.0"
52
- }
48
+ "scripts": {
49
+ "build": "tsc",
50
+ "generate": "openapi-ts",
51
+ "lint": "oxlint .",
52
+ "format": "oxfmt --write .",
53
+ "format:check": "oxfmt --check .",
54
+ "typecheck": "tsc --noEmit",
55
+ "test": "vitest run",
56
+ "test:coverage": "vitest run --coverage",
57
+ "check:spec-drift": "node scripts/check-spec-drift.mjs"
58
+ }
59
+ }