@prompty/openai 2.0.0-alpha.1 → 2.0.0-alpha.4

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
@@ -1,87 +1,87 @@
1
- # @prompty/openai
2
-
3
- OpenAI provider for Prompty — executor and processor for the OpenAI API.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @prompty/core @prompty/openai openai
9
- ```
10
-
11
- ## Usage
12
-
13
- Import `@prompty/openai` to auto-register the `openai` provider, then use `@prompty/core` as normal:
14
-
15
- ```typescript
16
- import "@prompty/openai";
17
- import { run } from "@prompty/core";
18
-
19
- const result = await run("./chat.prompty", {
20
- question: "What is quantum computing?",
21
- });
22
- ```
23
-
24
- ## `.prompty` Configuration
25
-
26
- Set `provider: openai` in your `.prompty` file:
27
-
28
- ```prompty
29
- ---
30
- name: my-prompt
31
- model:
32
- id: gpt-4o-mini
33
- provider: openai
34
- apiType: chat
35
- connection:
36
- kind: key
37
- endpoint: ${env:OPENAI_BASE_URL}
38
- apiKey: ${env:OPENAI_API_KEY}
39
- options:
40
- temperature: 0.7
41
- maxOutputTokens: 1000
42
- ---
43
- system:
44
- You are a helpful assistant.
45
-
46
- user:
47
- {{question}}
48
- ```
49
-
50
- ## Supported API Types
51
-
52
- | `apiType` | Description |
53
- |-----------|-------------|
54
- | `chat` (default) | Chat completions via `client.chat.completions.create()` |
55
- | `embedding` | Embeddings via `client.embeddings.create()` |
56
- | `image` | Image generation via `client.images.generate()` |
57
- | `responses` | Responses API via `client.responses.create()` |
58
-
59
- ## Streaming
60
-
61
- Enable streaming via `additionalProperties`:
62
-
63
- ```prompty
64
- model:
65
- options:
66
- additionalProperties:
67
- stream: true
68
- ```
69
-
70
- Returns a `PromptyStream` that can be iterated asynchronously.
71
-
72
- ## Exports
73
-
74
- | Export | Description |
75
- |--------|-------------|
76
- | `OpenAIExecutor` | Executor implementation for OpenAI |
77
- | `OpenAIProcessor` | Processor for OpenAI responses |
78
- | `processResponse` | Shared response processing helper |
79
- | `messageToWire` | Convert `Message` → OpenAI wire format |
80
- | `buildChatArgs` | Build chat completion arguments |
81
- | `buildEmbeddingArgs` | Build embedding arguments |
82
- | `buildImageArgs` | Build image generation arguments |
83
- | `buildResponsesArgs` | Build Responses API arguments |
84
-
85
- ## License
86
-
87
- MIT
1
+ # @prompty/openai
2
+
3
+ OpenAI provider for Prompty — executor and processor for the OpenAI API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @prompty/core @prompty/openai openai
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Import `@prompty/openai` to auto-register the `openai` provider, then use `@prompty/core` as normal:
14
+
15
+ ```typescript
16
+ import "@prompty/openai";
17
+ import { run } from "@prompty/core";
18
+
19
+ const result = await run("./chat.prompty", {
20
+ question: "What is quantum computing?",
21
+ });
22
+ ```
23
+
24
+ ## `.prompty` Configuration
25
+
26
+ Set `provider: openai` in your `.prompty` file:
27
+
28
+ ```prompty
29
+ ---
30
+ name: my-prompt
31
+ model:
32
+ id: gpt-4o-mini
33
+ provider: openai
34
+ apiType: chat
35
+ connection:
36
+ kind: key
37
+ endpoint: ${env:OPENAI_BASE_URL}
38
+ apiKey: ${env:OPENAI_API_KEY}
39
+ options:
40
+ temperature: 0.7
41
+ maxOutputTokens: 1000
42
+ ---
43
+ system:
44
+ You are a helpful assistant.
45
+
46
+ user:
47
+ {{question}}
48
+ ```
49
+
50
+ ## Supported API Types
51
+
52
+ | `apiType` | Description |
53
+ |-----------|-------------|
54
+ | `chat` (default) | Chat completions via `client.chat.completions.create()` |
55
+ | `embedding` | Embeddings via `client.embeddings.create()` |
56
+ | `image` | Image generation via `client.images.generate()` |
57
+ | `responses` | Responses API via `client.responses.create()` |
58
+
59
+ ## Streaming
60
+
61
+ Enable streaming via `additionalProperties`:
62
+
63
+ ```prompty
64
+ model:
65
+ options:
66
+ additionalProperties:
67
+ stream: true
68
+ ```
69
+
70
+ Returns a `PromptyStream` that can be iterated asynchronously.
71
+
72
+ ## Exports
73
+
74
+ | Export | Description |
75
+ |--------|-------------|
76
+ | `OpenAIExecutor` | Executor implementation for OpenAI |
77
+ | `OpenAIProcessor` | Processor for OpenAI responses |
78
+ | `processResponse` | Shared response processing helper |
79
+ | `messageToWire` | Convert `Message` → OpenAI wire format |
80
+ | `buildChatArgs` | Build chat completion arguments |
81
+ | `buildEmbeddingArgs` | Build embedding arguments |
82
+ | `buildImageArgs` | Build image generation arguments |
83
+ | `buildResponsesArgs` | Build Responses API arguments |
84
+
85
+ ## License
86
+
87
+ MIT
package/dist/index.cjs CHANGED
@@ -43,11 +43,13 @@ module.exports = __toCommonJS(index_exports);
43
43
 
44
44
  // src/executor.ts
45
45
  var import_openai = __toESM(require("openai"), 1);
46
- var import_core = require("@prompty/core");
47
46
  var import_core2 = require("@prompty/core");
48
47
  var import_core3 = require("@prompty/core");
48
+ var import_core4 = require("@prompty/core");
49
49
 
50
50
  // src/wire.ts
51
+ var import_core = require("@prompty/core");
52
+ var import_node_path = require("path");
51
53
  function messageToWire(msg) {
52
54
  const wire = { role: msg.role };
53
55
  for (const [k, v] of Object.entries(msg.metadata)) {
@@ -77,13 +79,26 @@ function partToWire(part) {
77
79
  type: "input_audio",
78
80
  input_audio: {
79
81
  data: part.source,
80
- ...part.mediaType && { format: part.mediaType }
82
+ ...part.mediaType && { format: mimeToAudioFormat(part.mediaType) }
81
83
  }
82
84
  };
83
85
  case "file":
84
86
  return { type: "file", file: { url: part.source } };
85
87
  }
86
88
  }
89
+ var AUDIO_MIME_MAP = {
90
+ "audio/wav": "wav",
91
+ "audio/mpeg": "mp3",
92
+ "audio/mp3": "mp3",
93
+ "audio/mp4": "mp4",
94
+ "audio/ogg": "ogg",
95
+ "audio/flac": "flac",
96
+ "audio/webm": "webm",
97
+ "audio/pcm": "pcm"
98
+ };
99
+ function mimeToAudioFormat(mediaType) {
100
+ return AUDIO_MIME_MAP[mediaType.toLowerCase()] ?? mediaType;
101
+ }
87
102
  function buildChatArgs(agent, messages) {
88
103
  const model = agent.model?.id || "gpt-4";
89
104
  const wireMessages = messages.map(messageToWire);
@@ -104,10 +119,24 @@ function buildChatArgs(agent, messages) {
104
119
  }
105
120
  function buildEmbeddingArgs(agent, data) {
106
121
  const model = agent.model?.id || "text-embedding-ada-002";
107
- const args = {
108
- input: Array.isArray(data) ? data : [data],
109
- model
110
- };
122
+ let input;
123
+ if (Array.isArray(data)) {
124
+ const texts = data.map((item) => {
125
+ if (typeof item === "string") return item;
126
+ if (item && typeof item === "object" && "text" in item) return item.text;
127
+ if (item && typeof item === "object" && "toTextContent" in item) {
128
+ const content = item.toTextContent();
129
+ return typeof content === "string" ? content : String(content);
130
+ }
131
+ return String(item);
132
+ });
133
+ input = texts.length === 1 ? texts[0] : texts;
134
+ } else if (typeof data === "string") {
135
+ input = data;
136
+ } else {
137
+ input = String(data);
138
+ }
139
+ const args = { input, model };
111
140
  const extra = agent.model?.options?.additionalProperties;
112
141
  if (extra) {
113
142
  for (const [k, v] of Object.entries(extra)) {
@@ -159,7 +188,7 @@ function buildOptions(agent) {
159
188
  if (opts.topP !== void 0) result.top_p = opts.topP;
160
189
  if (opts.frequencyPenalty !== void 0) result.frequency_penalty = opts.frequencyPenalty;
161
190
  if (opts.presencePenalty !== void 0) result.presence_penalty = opts.presencePenalty;
162
- if (opts.stopSequences !== void 0) result.stop = opts.stopSequences;
191
+ if (opts.stopSequences !== void 0 && opts.stopSequences.length > 0) result.stop = opts.stopSequences;
163
192
  if (opts.seed !== void 0) result.seed = opts.seed;
164
193
  if (opts.additionalProperties) {
165
194
  for (const [k, v] of Object.entries(opts.additionalProperties)) {
@@ -220,24 +249,64 @@ function toolsToWire(agent) {
220
249
  if (!tools || tools.length === 0) return [];
221
250
  const result = [];
222
251
  for (const t of tools) {
223
- if (t.kind !== "function") continue;
224
- const funcDef = { name: t.name };
225
- if (t.description) funcDef.description = t.description;
226
- const params = t.parameters;
227
- if (params && Array.isArray(params)) {
228
- funcDef.parameters = schemaToWire(params);
229
- }
230
- const strict = t.strict;
231
- if (strict) {
232
- funcDef.strict = true;
233
- if (funcDef.parameters) {
234
- funcDef.parameters.additionalProperties = false;
252
+ if (t.kind === "function") {
253
+ const funcDef = { name: t.name };
254
+ if (t.description) funcDef.description = t.description;
255
+ const boundNames = new Set((t.bindings ?? []).map((b) => b.name));
256
+ let params = t.parameters;
257
+ if (params && Array.isArray(params)) {
258
+ if (boundNames.size > 0) {
259
+ params = params.filter((p) => !boundNames.has(p.name));
260
+ }
261
+ funcDef.parameters = schemaToWire(params);
262
+ }
263
+ const strict = t.strict;
264
+ if (strict) {
265
+ funcDef.strict = true;
266
+ if (funcDef.parameters) {
267
+ funcDef.parameters.additionalProperties = false;
268
+ }
235
269
  }
270
+ result.push({ type: "function", function: funcDef });
271
+ } else if (t.kind === "prompty") {
272
+ const funcDef = projectPromptyTool(t, agent);
273
+ result.push({ type: "function", function: funcDef });
236
274
  }
237
- result.push({ type: "function", function: funcDef });
238
275
  }
239
276
  return result;
240
277
  }
278
+ function projectPromptyTool(tool, parent) {
279
+ const toolPath = tool.path;
280
+ if (!toolPath) {
281
+ throw new Error(`PromptyTool '${tool.name}' has no path`);
282
+ }
283
+ const parentPath = (parent.metadata ?? {}).__source_path;
284
+ if (!parentPath) {
285
+ throw new Error(
286
+ `Cannot resolve PromptyTool '${tool.name}': parent agent has no __source_path in metadata`
287
+ );
288
+ }
289
+ const childPath = (0, import_node_path.resolve)((0, import_node_path.dirname)(parentPath), toolPath);
290
+ const child = (0, import_core.load)(childPath);
291
+ const funcDef = { name: tool.name };
292
+ funcDef.description = tool.description || child.description || "";
293
+ const bindings = tool.bindings;
294
+ const boundNames = new Set((bindings ?? []).map((b) => b.name));
295
+ const childInputs = child.inputs ?? [];
296
+ let params = childInputs;
297
+ if (boundNames.size > 0) {
298
+ params = params.filter((p) => !boundNames.has(p.name));
299
+ }
300
+ funcDef.parameters = schemaToWire(params);
301
+ const strict = tool.strict;
302
+ if (strict) {
303
+ funcDef.strict = true;
304
+ if (funcDef.parameters) {
305
+ funcDef.parameters.additionalProperties = false;
306
+ }
307
+ }
308
+ return funcDef;
309
+ }
241
310
  function outputSchemaToWire(agent) {
242
311
  const outputs = agent.outputs;
243
312
  if (!outputs || outputs.length === 0) return null;
@@ -248,7 +317,7 @@ function outputSchemaToWire(agent) {
248
317
  properties[prop.name] = propertyToJsonSchema(prop);
249
318
  required.push(prop.name);
250
319
  }
251
- const name = (agent.name || "response").toLowerCase().replace(/[\s-]/g, "_");
320
+ const name = "structured_output";
252
321
  return {
253
322
  type: "json_schema",
254
323
  json_schema: {
@@ -295,6 +364,9 @@ function buildResponsesArgs(agent, messages) {
295
364
  }
296
365
  function messageToResponsesInput(msg) {
297
366
  const content = msg.toTextContent();
367
+ if (msg.metadata.responses_function_call) {
368
+ return msg.metadata.responses_function_call;
369
+ }
298
370
  if (msg.metadata.tool_call_id) {
299
371
  return {
300
372
  type: "function_call_output",
@@ -326,24 +398,41 @@ function responsesToolsToWire(agent) {
326
398
  if (!tools || tools.length === 0) return [];
327
399
  const result = [];
328
400
  for (const t of tools) {
329
- if (t.kind !== "function") continue;
330
- const tool = {
331
- type: "function",
332
- name: t.name
333
- };
334
- if (t.description) tool.description = t.description;
335
- const params = t.parameters;
336
- if (params && Array.isArray(params)) {
337
- tool.parameters = schemaToWire(params);
338
- }
339
- const strict = t.strict;
340
- if (strict) {
341
- tool.strict = true;
342
- if (tool.parameters) {
343
- tool.parameters.additionalProperties = false;
401
+ if (t.kind === "function") {
402
+ const tool = {
403
+ type: "function",
404
+ name: t.name
405
+ };
406
+ if (t.description) tool.description = t.description;
407
+ const params = t.parameters;
408
+ if (params && Array.isArray(params)) {
409
+ tool.parameters = schemaToWire(params);
410
+ }
411
+ const strict = t.strict;
412
+ if (strict) {
413
+ tool.strict = true;
414
+ if (tool.parameters) {
415
+ tool.parameters.additionalProperties = false;
416
+ }
344
417
  }
418
+ result.push(tool);
419
+ } else if (t.kind === "prompty") {
420
+ const projected = projectPromptyTool(t, agent);
421
+ const tool = {
422
+ type: "function",
423
+ name: projected.name
424
+ };
425
+ if (projected.description) tool.description = projected.description;
426
+ if (projected.parameters) tool.parameters = projected.parameters;
427
+ const strict = projected.strict;
428
+ if (strict) {
429
+ tool.strict = true;
430
+ if (tool.parameters) {
431
+ tool.parameters.additionalProperties = false;
432
+ }
433
+ }
434
+ result.push(tool);
345
435
  }
346
- result.push(tool);
347
436
  }
348
437
  return result;
349
438
  }
@@ -357,7 +446,7 @@ function outputSchemaToResponsesWire(agent) {
357
446
  properties[prop.name] = propertyToJsonSchema(prop);
358
447
  required.push(prop.name);
359
448
  }
360
- const name = (agent.name || "response").toLowerCase().replace(/[\s-]/g, "_");
449
+ const name = "structured_output";
361
450
  return {
362
451
  format: {
363
452
  type: "json_schema",
@@ -376,18 +465,18 @@ function outputSchemaToResponsesWire(agent) {
376
465
  // src/executor.ts
377
466
  var OpenAIExecutor = class {
378
467
  async execute(agent, messages) {
379
- return (0, import_core3.traceSpan)("OpenAIExecutor", async (emit) => {
468
+ return (0, import_core4.traceSpan)("OpenAIExecutor", async (emit) => {
380
469
  emit("signature", "prompty.openai.executor.OpenAIExecutor.invoke");
381
470
  emit("inputs", { data: messages });
382
471
  const client = this.resolveClient(agent);
383
472
  const clientName = client.constructor?.name ?? "OpenAI";
384
- await (0, import_core3.traceSpan)(clientName, async (ctorEmit) => {
473
+ await (0, import_core4.traceSpan)(clientName, async (ctorEmit) => {
385
474
  ctorEmit("signature", `${clientName}.ctor`);
386
475
  const conn = agent.model?.connection;
387
- if (conn instanceof import_core.ReferenceConnection) {
476
+ if (conn instanceof import_core2.ReferenceConnection) {
388
477
  ctorEmit("inputs", { source: "reference", name: conn.name });
389
478
  } else {
390
- ctorEmit("inputs", (0, import_core3.sanitizeValue)("ctor", this.clientKwargs(agent)));
479
+ ctorEmit("inputs", (0, import_core4.sanitizeValue)("ctor", this.clientKwargs(agent)));
391
480
  }
392
481
  ctorEmit("result", clientName);
393
482
  });
@@ -400,18 +489,17 @@ var OpenAIExecutor = class {
400
489
  /** Dispatch to the appropriate API and trace the call. */
401
490
  async executeApiCall(client, clientName, agent, messages, apiType) {
402
491
  switch (apiType) {
403
- case "chat":
404
- case "agent": {
492
+ case "chat": {
405
493
  const args = buildChatArgs(agent, messages);
406
494
  const isStreaming = !!args.stream;
407
- return (0, import_core3.traceSpan)("create", async (callEmit) => {
495
+ return (0, import_core4.traceSpan)("create", async (callEmit) => {
408
496
  callEmit("signature", `${clientName}.chat.completions.create`);
409
- callEmit("inputs", (0, import_core3.sanitizeValue)("create", args));
497
+ callEmit("inputs", (0, import_core4.sanitizeValue)("create", args));
410
498
  const result = await client.chat.completions.create(
411
499
  args
412
500
  );
413
501
  if (isStreaming) {
414
- return new import_core.PromptyStream(`${clientName}Executor`, result);
502
+ return new import_core2.PromptyStream(`${clientName}Executor`, result);
415
503
  }
416
504
  callEmit("result", result);
417
505
  return result;
@@ -419,9 +507,9 @@ var OpenAIExecutor = class {
419
507
  }
420
508
  case "embedding": {
421
509
  const args = buildEmbeddingArgs(agent, messages);
422
- return (0, import_core3.traceSpan)("create", async (callEmit) => {
510
+ return (0, import_core4.traceSpan)("create", async (callEmit) => {
423
511
  callEmit("signature", `${clientName}.embeddings.create`);
424
- callEmit("inputs", (0, import_core3.sanitizeValue)("create", args));
512
+ callEmit("inputs", (0, import_core4.sanitizeValue)("create", args));
425
513
  const result = await client.embeddings.create(
426
514
  args
427
515
  );
@@ -431,9 +519,9 @@ var OpenAIExecutor = class {
431
519
  }
432
520
  case "image": {
433
521
  const args = buildImageArgs(agent, messages);
434
- return (0, import_core3.traceSpan)("generate", async (callEmit) => {
522
+ return (0, import_core4.traceSpan)("generate", async (callEmit) => {
435
523
  callEmit("signature", `${clientName}.images.generate`);
436
- callEmit("inputs", (0, import_core3.sanitizeValue)("generate", args));
524
+ callEmit("inputs", (0, import_core4.sanitizeValue)("generate", args));
437
525
  const result = await client.images.generate(
438
526
  args
439
527
  );
@@ -444,14 +532,14 @@ var OpenAIExecutor = class {
444
532
  case "responses": {
445
533
  const args = buildResponsesArgs(agent, messages);
446
534
  const isStreaming = !!args.stream;
447
- return (0, import_core3.traceSpan)("create", async (callEmit) => {
535
+ return (0, import_core4.traceSpan)("create", async (callEmit) => {
448
536
  callEmit("signature", `${clientName}.responses.create`);
449
- callEmit("inputs", (0, import_core3.sanitizeValue)("create", args));
537
+ callEmit("inputs", (0, import_core4.sanitizeValue)("create", args));
450
538
  const result = await client.responses.create(
451
539
  args
452
540
  );
453
541
  if (isStreaming) {
454
- return new import_core.PromptyStream(`${clientName}Executor`, result);
542
+ return new import_core2.PromptyStream(`${clientName}Executor`, result);
455
543
  }
456
544
  callEmit("result", result);
457
545
  return result;
@@ -463,8 +551,8 @@ var OpenAIExecutor = class {
463
551
  }
464
552
  resolveClient(agent) {
465
553
  const conn = agent.model?.connection;
466
- if (conn instanceof import_core.ReferenceConnection) {
467
- return (0, import_core2.getConnection)(conn.name);
554
+ if (conn instanceof import_core2.ReferenceConnection) {
555
+ return (0, import_core3.getConnection)(conn.name);
468
556
  }
469
557
  const kwargs = this.clientKwargs(agent);
470
558
  return new import_openai.default(kwargs);
@@ -472,19 +560,23 @@ var OpenAIExecutor = class {
472
560
  clientKwargs(agent) {
473
561
  const kwargs = {};
474
562
  const conn = agent.model?.connection;
475
- if (conn instanceof import_core.ApiKeyConnection) {
563
+ if (conn instanceof import_core2.ApiKeyConnection) {
476
564
  if (conn.apiKey) kwargs.apiKey = conn.apiKey;
477
565
  if (conn.endpoint) kwargs.baseURL = conn.endpoint;
566
+ } else if (conn) {
567
+ throw new Error(
568
+ `Connection kind '${conn.kind}' is not supported by the OpenAI executor. Use 'key' for API key auth or 'reference' with registerConnection() for pre-configured clients.`
569
+ );
478
570
  }
479
571
  return kwargs;
480
572
  }
481
573
  };
482
574
 
483
575
  // src/processor.ts
484
- var import_core4 = require("@prompty/core");
576
+ var import_core5 = require("@prompty/core");
485
577
  var OpenAIProcessor = class {
486
578
  async process(agent, response) {
487
- return (0, import_core4.traceSpan)("OpenAIProcessor", async (emit) => {
579
+ return (0, import_core5.traceSpan)("OpenAIProcessor", async (emit) => {
488
580
  emit("signature", "prompty.openai.processor.OpenAIProcessor.invoke");
489
581
  emit("inputs", { data: response });
490
582
  const result = processResponse(agent, response);
@@ -628,7 +720,11 @@ function processChatCompletion(agent, response) {
628
720
  });
629
721
  }
630
722
  const content = message.content;
631
- if (content === null) return null;
723
+ if (content === null) {
724
+ const refusal = message.refusal;
725
+ if (typeof refusal === "string") return refusal;
726
+ return null;
727
+ }
632
728
  if (agent.outputs && agent.outputs.length > 0) {
633
729
  try {
634
730
  return JSON.parse(content);
@@ -654,9 +750,9 @@ function processImage(response) {
654
750
  }
655
751
 
656
752
  // src/index.ts
657
- var import_core5 = require("@prompty/core");
658
- (0, import_core5.registerExecutor)("openai", new OpenAIExecutor());
659
- (0, import_core5.registerProcessor)("openai", new OpenAIProcessor());
753
+ var import_core6 = require("@prompty/core");
754
+ (0, import_core6.registerExecutor)("openai", new OpenAIExecutor());
755
+ (0, import_core6.registerProcessor)("openai", new OpenAIProcessor());
660
756
  // Annotate the CommonJS export names for ESM import in node:
661
757
  0 && (module.exports = {
662
758
  OpenAIExecutor,