@hardlydifficult/ai-msg 1.0.1 → 1.0.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.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # @hardlydifficult/ai-msg
2
2
 
3
- Opinionated utilities for extracting structured output from AI text responses.
4
-
5
- LLM responses often wrap structured data in markdown code blocks, preamble text, or trailing commentary. This package reliably extracts JSON and code blocks from messy AI output.
3
+ Extract structured data from AI model responses: JSON, typed schemas, code blocks, and multimodal content.
6
4
 
7
5
  ## Installation
8
6
 
@@ -10,60 +8,51 @@ LLM responses often wrap structured data in markdown code blocks, preamble text,
10
8
  npm install @hardlydifficult/ai-msg
11
9
  ```
12
10
 
13
- For typed extraction with Zod validation:
14
-
15
- ```bash
16
- npm install @hardlydifficult/ai-msg zod
17
- ```
18
-
19
- ## Usage
11
+ ## API
20
12
 
21
- ### `extractJson(text, sentinel?): unknown[]`
13
+ ### `extractJson(text: string, sentinel?: string): unknown[]`
22
14
 
23
- Extracts all valid JSON values from text using a three-pass strategy:
15
+ Extract JSON objects/arrays from AI response text. Uses a three-pass strategy:
24
16
 
25
- 1. Direct `JSON.parse` of the trimmed text
26
- 2. Search markdown code blocks (json-tagged first, then any)
27
- 3. Find all balanced `{}` or `[]` substrings in prose
17
+ 1. Parse the entire text as JSON
18
+ 2. Extract from fenced code blocks (json-tagged first, then any)
19
+ 3. Find balanced `{}`/`[]` in prose
28
20
 
29
- Always returns an array. Returns `[]` when no JSON is found.
21
+ Pass a `sentinel` string to return `[]` when the response contains it (useful for "no results" markers).
30
22
 
31
23
  ````typescript
32
24
  import { extractJson } from "@hardlydifficult/ai-msg";
33
25
 
34
- extractJson('{"key": "value"}');
35
- // [{ key: "value" }]
36
-
37
26
  extractJson('Here is the result:\n```json\n{"key": "value"}\n```\nDone.');
38
27
  // [{ key: "value" }]
39
28
 
40
- extractJson('The answer is {"key": "value"} as shown.');
41
- // [{ key: "value" }]
29
+ extractJson('First {"a": 1} then {"b": 2} done.');
30
+ // [{ a: 1 }, { b: 2 }]
42
31
 
43
- extractJson("no json here");
32
+ extractJson("NO_FINDINGS: scan completed.", "NO_FINDINGS");
44
33
  // []
45
34
  ````
46
35
 
47
- Multiple JSON values are returned when present:
36
+ ### `extractTyped<T>(text: string, schema: SchemaLike<T>, sentinel?: string): T[]`
37
+
38
+ Extract and validate JSON against a schema. Works with Zod 3, Zod 4, or any object with a `safeParse` method. Only returns entries that pass validation.
48
39
 
49
40
  ```typescript
50
- extractJson('First {"a": 1} then {"b": 2} done.');
51
- // [{ a: 1 }, { b: 2 }]
52
- ```
41
+ import { extractTyped } from "@hardlydifficult/ai-msg";
42
+ import { z } from "zod";
53
43
 
54
- The optional `sentinel` parameter short-circuits extraction — if the text contains the sentinel string, an empty array is returned immediately:
44
+ const Person = z.object({ name: z.string(), age: z.number() });
55
45
 
56
- ```typescript
57
- extractJson("NO_FINDINGS: the scan completed with no issues.", "NO_FINDINGS");
58
- // []
46
+ extractTyped('{"name": "Alice", "age": 30}', Person);
47
+ // [{ name: "Alice", age: 30 }]
59
48
 
60
- extractJson('{"result": "found"}', "NO_FINDINGS");
61
- // [{ result: "found" }]
49
+ extractTyped('{"name": "Alice", "age": "thirty"}', Person);
50
+ // [] fails validation
62
51
  ```
63
52
 
64
- ### `extractCodeBlock(text, lang?): string[]`
53
+ ### `extractCodeBlock(text: string, lang?: string): string[]`
65
54
 
66
- Extracts all markdown code blocks from text. Optionally filters by language tag.
55
+ Extract fenced code block contents. Optionally filter by language tag.
67
56
 
68
57
  ````typescript
69
58
  import { extractCodeBlock } from "@hardlydifficult/ai-msg";
@@ -73,27 +62,22 @@ extractCodeBlock("```ts\nconst x = 1;\n```");
73
62
 
74
63
  extractCodeBlock("```json\n{}\n```\n```ts\nconst x = 1;\n```", "json");
75
64
  // ["{}"]
76
-
77
- extractCodeBlock("no code blocks");
78
- // []
79
65
  ````
80
66
 
81
- ### `extractTyped<T>(text, schema, sentinel?): T[]`
67
+ ### `extractTextContent(content: string | { type: string; text?: string }[]): string`
82
68
 
83
- Extracts all JSON values from text and validates each against a Zod schema. Only values that pass validation are included. Requires `zod` as a peer dependency.
69
+ Extract plain text from a message content field. Handles both plain strings and multimodal content arrays (AI SDK format).
84
70
 
85
71
  ```typescript
86
- import { extractTyped } from "@hardlydifficult/ai-msg";
87
- import { z } from "zod";
72
+ import { extractTextContent } from "@hardlydifficult/ai-msg";
88
73
 
89
- const Person = z.object({ name: z.string(), age: z.number() });
90
-
91
- extractTyped('{"name": "Alice", "age": 30}', Person);
92
- // [{ name: "Alice", age: 30 }]
74
+ extractTextContent("hello"); // "hello"
75
+ extractTextContent([
76
+ { type: "text", text: "hello" },
77
+ { type: "image_url", url: "..." },
78
+ ]); // "hello"
79
+ ```
93
80
 
94
- extractTyped('{"name": "Alice", "age": "thirty"}', Person);
95
- // []
81
+ ### `toPlainTextMessages(messages: MultimodalMessage[]): { role, content }[]`
96
82
 
97
- extractTyped("NO_FINDINGS", Person, "NO_FINDINGS");
98
- // []
99
- ```
83
+ Convert multimodal messages to plain text messages by flattening content arrays.
@@ -1,2 +1,3 @@
1
+ /** Extracts the contents of fenced code blocks from a string, optionally filtered by language tag. */
1
2
  export declare function extractCodeBlock(text: string, lang?: string): string[];
2
3
  //# sourceMappingURL=extractCodeBlock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extractCodeBlock.d.ts","sourceRoot":"","sources":["../src/extractCodeBlock.ts"],"names":[],"mappings":"AAEA,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAatE"}
1
+ {"version":3,"file":"extractCodeBlock.d.ts","sourceRoot":"","sources":["../src/extractCodeBlock.ts"],"names":[],"mappings":"AAEA,sGAAsG;AACtG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAatE"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extractCodeBlock = extractCodeBlock;
4
4
  const codeBlockRegex = /^```(\w*)\s*\n([\s\S]*?)^```/gm;
5
+ /** Extracts the contents of fenced code blocks from a string, optionally filtered by language tag. */
5
6
  function extractCodeBlock(text, lang) {
6
7
  const results = [];
7
8
  for (const match of text.matchAll(codeBlockRegex)) {
@@ -1 +1 @@
1
- {"version":3,"file":"extractCodeBlock.js","sourceRoot":"","sources":["../src/extractCodeBlock.ts"],"names":[],"mappings":";;AAEA,4CAaC;AAfD,MAAM,cAAc,GAAG,gCAAgC,CAAC;AAExD,SAAgB,gBAAgB,CAAC,IAAY,EAAE,IAAa;IAC1D,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QAClD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACnE,SAAS;QACX,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"extractCodeBlock.js","sourceRoot":"","sources":["../src/extractCodeBlock.ts"],"names":[],"mappings":";;AAGA,4CAaC;AAhBD,MAAM,cAAc,GAAG,gCAAgC,CAAC;AAExD,sGAAsG;AACtG,SAAgB,gBAAgB,CAAC,IAAY,EAAE,IAAa;IAC1D,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QAClD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACnE,SAAS;QACX,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -1,2 +1,3 @@
1
+ /** Extracts JSON values from text using progressive strategies: direct parse, code blocks, then balanced-brace scanning. */
1
2
  export declare function extractJson(text: string, sentinel?: string): unknown[];
2
3
  //# sourceMappingURL=extractJson.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extractJson.d.ts","sourceRoot":"","sources":["../src/extractJson.ts"],"names":[],"mappings":"AAWA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CAgDtE"}
1
+ {"version":3,"file":"extractJson.d.ts","sourceRoot":"","sources":["../src/extractJson.ts"],"names":[],"mappings":"AAWA,4HAA4H;AAC5H,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CAgDtE"}
@@ -11,6 +11,7 @@ function tryParse(text) {
11
11
  return undefined;
12
12
  }
13
13
  }
14
+ /** Extracts JSON values from text using progressive strategies: direct parse, code blocks, then balanced-brace scanning. */
14
15
  function extractJson(text, sentinel) {
15
16
  // Sentinel check — if the text contains the sentinel, treat as "no findings"
16
17
  if (sentinel !== undefined && text.includes(sentinel)) {
@@ -1 +1 @@
1
- {"version":3,"file":"extractJson.js","sourceRoot":"","sources":["../src/extractJson.ts"],"names":[],"mappings":";;AAWA,kCAgDC;AA3DD,+DAAyD;AACzD,uDAAoD;AAEpD,SAAS,QAAQ,CAAC,IAAY;IAC5B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAgB,WAAW,CAAC,IAAY,EAAE,QAAiB;IACzD,6EAA6E;IAC7E,IAAI,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,6BAA6B;IAC7B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACrC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,CAAC,MAAM,CAAC,CAAC;IAClB,CAAC;IAED,oDAAoD;IACpD,MAAM,UAAU,GAAc,EAAE,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,IAAA,sCAAgB,EAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,KAAK,MAAM,KAAK,IAAI,IAAA,sCAAgB,EAAC,IAAI,CAAC,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,kDAAkD;IAClD,MAAM,OAAO,GAAc,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,IAAA,iCAAe,EAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,IAAA,iCAAe,EAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"extractJson.js","sourceRoot":"","sources":["../src/extractJson.ts"],"names":[],"mappings":";;AAYA,kCAgDC;AA5DD,+DAAyD;AACzD,uDAAoD;AAEpD,SAAS,QAAQ,CAAC,IAAY;IAC5B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,4HAA4H;AAC5H,SAAgB,WAAW,CAAC,IAAY,EAAE,QAAiB;IACzD,6EAA6E;IAC7E,IAAI,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,6BAA6B;IAC7B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACrC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,CAAC,MAAM,CAAC,CAAC;IAClB,CAAC;IAED,oDAAoD;IACpD,MAAM,UAAU,GAAc,EAAE,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,IAAA,sCAAgB,EAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,KAAK,MAAM,KAAK,IAAI,IAAA,sCAAgB,EAAC,IAAI,CAAC,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,kDAAkD;IAClD,MAAM,OAAO,GAAc,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,IAAA,iCAAe,EAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,IAAA,iCAAe,EAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -1,3 +1,16 @@
1
- import { type ZodType } from "zod";
2
- export declare function extractTyped<T>(text: string, schema: ZodType<T>, sentinel?: string): T[];
1
+ /**
2
+ * Any schema with a safeParse method (e.g. Zod 3, Zod 4, or custom).
3
+ * Using a structural type avoids coupling to a specific Zod version.
4
+ */
5
+ export interface SchemaLike<T> {
6
+ safeParse(data: unknown): {
7
+ success: true;
8
+ data: T;
9
+ } | {
10
+ success: false;
11
+ error?: unknown;
12
+ };
13
+ }
14
+ /** Extracts JSON from text and validates each result against a schema, returning only values that pass. */
15
+ export declare function extractTyped<T>(text: string, schema: SchemaLike<T>, sentinel?: string): T[];
3
16
  //# sourceMappingURL=extractTyped.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extractTyped.d.ts","sourceRoot":"","sources":["../src/extractTyped.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,KAAK,CAAC;AAInC,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,CAAC,EAAE,CAUL"}
1
+ {"version":3,"file":"extractTyped.d.ts","sourceRoot":"","sources":["../src/extractTyped.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,SAAS,CACP,IAAI,EAAE,OAAO,GACZ;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACrE;AAED,2GAA2G;AAC3G,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EACrB,QAAQ,CAAC,EAAE,MAAM,GAChB,CAAC,EAAE,CAUL"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extractTyped = extractTyped;
4
4
  const extractJson_js_1 = require("./extractJson.js");
5
+ /** Extracts JSON from text and validates each result against a schema, returning only values that pass. */
5
6
  function extractTyped(text, schema, sentinel) {
6
7
  const results = (0, extractJson_js_1.extractJson)(text, sentinel);
7
8
  const validated = [];
@@ -1 +1 @@
1
- {"version":3,"file":"extractTyped.js","sourceRoot":"","sources":["../src/extractTyped.ts"],"names":[],"mappings":";;AAIA,oCAcC;AAhBD,qDAA+C;AAE/C,SAAgB,YAAY,CAC1B,IAAY,EACZ,MAAkB,EAClB,QAAiB;IAEjB,MAAM,OAAO,GAAG,IAAA,4BAAW,EAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAQ,EAAE,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"extractTyped.js","sourceRoot":"","sources":["../src/extractTyped.ts"],"names":[],"mappings":";;AAaA,oCAcC;AA3BD,qDAA+C;AAY/C,2GAA2G;AAC3G,SAAgB,YAAY,CAC1B,IAAY,EACZ,MAAqB,EACrB,QAAiB;IAEjB,MAAM,OAAO,GAAG,IAAA,4BAAW,EAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAQ,EAAE,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -1,3 +1,5 @@
1
+ /** Finds the first substring enclosed by balanced open/close characters, respecting JSON string escaping. */
1
2
  export declare function findBalanced(text: string, openChar: string, closeChar: string): string | null;
3
+ /** Finds all non-overlapping substrings enclosed by balanced open/close characters. */
2
4
  export declare function findAllBalanced(text: string, openChar: string, closeChar: string): string[];
3
5
  //# sourceMappingURL=findBalanced.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"findBalanced.d.ts","sourceRoot":"","sources":["../src/findBalanced.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CA6Cf;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,MAAM,EAAE,CAiBV"}
1
+ {"version":3,"file":"findBalanced.d.ts","sourceRoot":"","sources":["../src/findBalanced.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CA6Cf;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,MAAM,EAAE,CAiBV"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.findBalanced = findBalanced;
4
4
  exports.findAllBalanced = findAllBalanced;
5
+ /** Finds the first substring enclosed by balanced open/close characters, respecting JSON string escaping. */
5
6
  function findBalanced(text, openChar, closeChar) {
6
7
  const start = text.indexOf(openChar);
7
8
  if (start === -1) {
@@ -41,6 +42,7 @@ function findBalanced(text, openChar, closeChar) {
41
42
  }
42
43
  return null;
43
44
  }
45
+ /** Finds all non-overlapping substrings enclosed by balanced open/close characters. */
44
46
  function findAllBalanced(text, openChar, closeChar) {
45
47
  const results = [];
46
48
  let offset = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"findBalanced.js","sourceRoot":"","sources":["../src/findBalanced.ts"],"names":[],"mappings":";;AAAA,oCAiDC;AAED,0CAqBC;AAxED,SAAgB,YAAY,CAC1B,IAAY,EACZ,QAAgB,EAChB,SAAiB;IAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEnB,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,GAAG,KAAK,CAAC;YAChB,SAAS;QACX,CAAC;QAED,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAChB,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,QAAQ,GAAG,CAAC,QAAQ,CAAC;YACrB,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACb,SAAS;QACX,CAAC;QAED,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;YACpB,KAAK,EAAE,CAAC;QACV,CAAC;aAAM,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YAC5B,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAgB,eAAe,CAC7B,IAAY,EACZ,QAAgB,EAChB,SAAiB;IAEjB,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,OAAO,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,YAAY,CAAC,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC3D,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,MAAM;QACR,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,IAAI,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC;IACtC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"findBalanced.js","sourceRoot":"","sources":["../src/findBalanced.ts"],"names":[],"mappings":";;AACA,oCAiDC;AAGD,0CAqBC;AA1ED,6GAA6G;AAC7G,SAAgB,YAAY,CAC1B,IAAY,EACZ,QAAgB,EAChB,SAAiB;IAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEnB,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,GAAG,KAAK,CAAC;YAChB,SAAS;QACX,CAAC;QAED,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAChB,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,QAAQ,GAAG,CAAC,QAAQ,CAAC;YACrB,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACb,SAAS;QACX,CAAC;QAED,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;YACpB,KAAK,EAAE,CAAC;QACV,CAAC;aAAM,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YAC5B,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,uFAAuF;AACvF,SAAgB,eAAe,CAC7B,IAAY,EACZ,QAAgB,EAChB,SAAiB;IAEjB,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,OAAO,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,YAAY,CAAC,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC3D,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,MAAM;QACR,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,IAAI,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC;IACtC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { extractCodeBlock } from "./extractCodeBlock.js";
2
2
  export { extractJson } from "./extractJson.js";
3
- export { extractTyped } from "./extractTyped.js";
3
+ export { extractTyped, type SchemaLike } from "./extractTyped.js";
4
+ export { extractTextContent, toPlainTextMessages, type MultimodalMessage, } from "./multimodal.js";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractTyped = exports.extractJson = exports.extractCodeBlock = void 0;
3
+ exports.toPlainTextMessages = exports.extractTextContent = exports.extractTyped = exports.extractJson = exports.extractCodeBlock = void 0;
4
4
  var extractCodeBlock_js_1 = require("./extractCodeBlock.js");
5
5
  Object.defineProperty(exports, "extractCodeBlock", { enumerable: true, get: function () { return extractCodeBlock_js_1.extractCodeBlock; } });
6
6
  var extractJson_js_1 = require("./extractJson.js");
7
7
  Object.defineProperty(exports, "extractJson", { enumerable: true, get: function () { return extractJson_js_1.extractJson; } });
8
8
  var extractTyped_js_1 = require("./extractTyped.js");
9
9
  Object.defineProperty(exports, "extractTyped", { enumerable: true, get: function () { return extractTyped_js_1.extractTyped; } });
10
+ var multimodal_js_1 = require("./multimodal.js");
11
+ Object.defineProperty(exports, "extractTextContent", { enumerable: true, get: function () { return multimodal_js_1.extractTextContent; } });
12
+ Object.defineProperty(exports, "toPlainTextMessages", { enumerable: true, get: function () { return multimodal_js_1.toPlainTextMessages; } });
10
13
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6DAAyD;AAAhD,uHAAA,gBAAgB,OAAA;AACzB,mDAA+C;AAAtC,6GAAA,WAAW,OAAA;AACpB,qDAAiD;AAAxC,+GAAA,YAAY,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6DAAyD;AAAhD,uHAAA,gBAAgB,OAAA;AACzB,mDAA+C;AAAtC,6GAAA,WAAW,OAAA;AACpB,qDAAkE;AAAzD,+GAAA,YAAY,OAAA;AACrB,iDAIyB;AAHvB,mHAAA,kBAAkB,OAAA;AAClB,oHAAA,mBAAmB,OAAA"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Structural type for messages that may contain multimodal content.
3
+ * Compatible with AI SDK message formats and ChatMessage from @ai/shared.
4
+ */
5
+ export interface MultimodalMessage {
6
+ role: "system" | "user" | "assistant";
7
+ content: string | {
8
+ type: string;
9
+ text?: string;
10
+ }[];
11
+ }
12
+ /**
13
+ * Extract text content from a message content field.
14
+ * Handles both plain string and multimodal content arrays.
15
+ */
16
+ export declare function extractTextContent(content: MultimodalMessage["content"]): string;
17
+ /**
18
+ * Convert multimodal messages to plain text messages.
19
+ * Flattens any multimodal content arrays to plain text strings.
20
+ */
21
+ export declare function toPlainTextMessages(messages: MultimodalMessage[]): {
22
+ role: "system" | "user" | "assistant";
23
+ content: string;
24
+ }[];
25
+ //# sourceMappingURL=multimodal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multimodal.d.ts","sourceRoot":"","sources":["../src/multimodal.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,OAAO,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACrD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,iBAAiB,CAAC,SAAS,CAAC,GACpC,MAAM,CAWR;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,iBAAiB,EAAE,GAC5B;IAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CAK9D"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractTextContent = extractTextContent;
4
+ exports.toPlainTextMessages = toPlainTextMessages;
5
+ /**
6
+ * Extract text content from a message content field.
7
+ * Handles both plain string and multimodal content arrays.
8
+ */
9
+ function extractTextContent(content) {
10
+ if (typeof content === "string") {
11
+ return content;
12
+ }
13
+ return content
14
+ .filter((c) => c.type === "text" && typeof c.text === "string")
15
+ .map((c) => c.text)
16
+ .join("\n");
17
+ }
18
+ /**
19
+ * Convert multimodal messages to plain text messages.
20
+ * Flattens any multimodal content arrays to plain text strings.
21
+ */
22
+ function toPlainTextMessages(messages) {
23
+ return messages.map((m) => ({
24
+ role: m.role,
25
+ content: extractTextContent(m.content),
26
+ }));
27
+ }
28
+ //# sourceMappingURL=multimodal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multimodal.js","sourceRoot":"","sources":["../src/multimodal.ts"],"names":[],"mappings":";;AAaA,gDAaC;AAMD,kDAOC;AA9BD;;;GAGG;AACH,SAAgB,kBAAkB,CAChC,OAAqC;IAErC,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,OAAO;SACX,MAAM,CACL,CAAC,CAAC,EAAuC,EAAE,CACzC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAClD;SACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SAClB,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAgB,mBAAmB,CACjC,QAA6B;IAE7B,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC;KACvC,CAAC,CAAC,CAAC;AACN,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/ai-msg",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [
@@ -14,14 +14,6 @@
14
14
  "lint": "tsc --noEmit",
15
15
  "clean": "rm -rf dist"
16
16
  },
17
- "peerDependencies": {
18
- "zod": ">=3.0.0"
19
- },
20
- "peerDependenciesMeta": {
21
- "zod": {
22
- "optional": true
23
- }
24
- },
25
17
  "devDependencies": {
26
18
  "typescript": "5.8.3",
27
19
  "vitest": "1.6.1",