@hardlydifficult/ai 1.0.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 (42) hide show
  1. package/README.md +139 -0
  2. package/dist/claude.d.ts +11 -0
  3. package/dist/claude.d.ts.map +1 -0
  4. package/dist/claude.js +15 -0
  5. package/dist/claude.js.map +1 -0
  6. package/dist/createAI.d.ts +6 -0
  7. package/dist/createAI.d.ts.map +1 -0
  8. package/dist/createAI.js +100 -0
  9. package/dist/createAI.js.map +1 -0
  10. package/dist/extractCodeBlock.d.ts +3 -0
  11. package/dist/extractCodeBlock.d.ts.map +1 -0
  12. package/dist/extractCodeBlock.js +18 -0
  13. package/dist/extractCodeBlock.js.map +1 -0
  14. package/dist/extractJson.d.ts +3 -0
  15. package/dist/extractJson.d.ts.map +1 -0
  16. package/dist/extractJson.js +60 -0
  17. package/dist/extractJson.js.map +1 -0
  18. package/dist/extractTyped.d.ts +16 -0
  19. package/dist/extractTyped.d.ts.map +1 -0
  20. package/dist/extractTyped.js +17 -0
  21. package/dist/extractTyped.js.map +1 -0
  22. package/dist/findBalanced.d.ts +5 -0
  23. package/dist/findBalanced.d.ts.map +1 -0
  24. package/dist/findBalanced.js +61 -0
  25. package/dist/findBalanced.js.map +1 -0
  26. package/dist/index.d.ts +9 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +19 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/multimodal.d.ts +25 -0
  31. package/dist/multimodal.d.ts.map +1 -0
  32. package/dist/multimodal.js +28 -0
  33. package/dist/multimodal.js.map +1 -0
  34. package/dist/ollama.d.ts +4 -0
  35. package/dist/ollama.d.ts.map +1 -0
  36. package/dist/ollama.js +9 -0
  37. package/dist/ollama.js.map +1 -0
  38. package/dist/types.d.ts +25 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +3 -0
  41. package/dist/types.js.map +1 -0
  42. package/package.json +35 -0
package/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # @hardlydifficult/ai
2
+
3
+ Unified AI client with chainable structured output, required usage tracking, and response parsing utilities.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @hardlydifficult/ai
9
+ ```
10
+
11
+ ## AI Client
12
+
13
+ ### `createAI(model, tracker, options?)`
14
+
15
+ Creates an AI client. Usage tracking is **required** — every call automatically fires `tracker.record()`.
16
+
17
+ ```typescript
18
+ import { createAI, claude } from "@hardlydifficult/ai";
19
+
20
+ const ai = createAI(claude("sonnet"), tracker);
21
+ ```
22
+
23
+ ### `chat(prompt, systemPrompt?)`
24
+
25
+ Send a prompt and get a rich response with usage info and conversation support.
26
+
27
+ ```typescript
28
+ const msg = await ai.chat("Explain closures");
29
+ msg.text; // "A closure is..."
30
+ msg.usage; // { inputTokens: 50, outputTokens: 200, durationMs: 1200 }
31
+
32
+ // With system prompt
33
+ const msg = await ai.chat("Explain closures", "You are a TypeScript tutor");
34
+ ```
35
+
36
+ ### `.zod(schema)` — Structured Output
37
+
38
+ Chain `.zod(schema)` to constrain the model's output format AND validate the result. The schema does triple duty: constrains output, validates response, provides TypeScript types.
39
+
40
+ ```typescript
41
+ import { z } from "zod";
42
+
43
+ const TaskSchema = z.object({
44
+ title: z.string(),
45
+ priority: z.enum(["high", "medium", "low"]),
46
+ });
47
+
48
+ const msg = await ai.chat("Create a task for fixing the login bug").zod(TaskSchema);
49
+ msg.data; // { title: "Fix login bug", priority: "high" } — typed as z.infer<typeof TaskSchema>
50
+ ```
51
+
52
+ ### `.reply(prompt)` — Conversation
53
+
54
+ Continue a conversation. Message history accumulates automatically.
55
+
56
+ ```typescript
57
+ const msg1 = await ai.chat("What is a monad?");
58
+ const msg2 = await msg1.reply("Give me a TypeScript example");
59
+ const msg3 = await msg2.reply("Now formalize it").zod(DefinitionSchema);
60
+ ```
61
+
62
+ ## Model Helpers
63
+
64
+ ### `claude(variant)`
65
+
66
+ Short names for Anthropic models. Uses auto-resolving aliases — always gets the latest snapshot.
67
+
68
+ ```typescript
69
+ import { claude } from "@hardlydifficult/ai";
70
+
71
+ claude("sonnet"); // claude-sonnet-4-5 → latest snapshot
72
+ claude("haiku"); // claude-haiku-4-5
73
+ claude("opus"); // claude-opus-4-6
74
+ ```
75
+
76
+ ### `ollama(model)`
77
+
78
+ Ollama models. Names match whatever is installed locally.
79
+
80
+ ```typescript
81
+ import { ollama } from "@hardlydifficult/ai";
82
+
83
+ ollama("qwen3-coder-next:latest");
84
+ ollama("llama3.3");
85
+ ```
86
+
87
+ ## Usage Tracking
88
+
89
+ `createAI` requires an `AITracker` — no AI without tracking. The tracker fires for every call (`chat`, `reply`, `zod`).
90
+
91
+ ```typescript
92
+ import type { AITracker } from "@hardlydifficult/ai";
93
+
94
+ const tracker: AITracker = {
95
+ record({ inputTokens, outputTokens, durationMs }) {
96
+ const cost = inputTokens * 3 / 1_000_000 + outputTokens * 15 / 1_000_000;
97
+ console.log(`Cost: $${cost.toFixed(4)}`);
98
+ },
99
+ };
100
+
101
+ const ai = createAI(claude("sonnet"), tracker);
102
+ ```
103
+
104
+ ## Response Parsing
105
+
106
+ ### `extractJson(text, sentinel?)`
107
+
108
+ Extract JSON from AI response text using a three-pass strategy: direct parse, code blocks, balanced braces.
109
+
110
+ ```typescript
111
+ import { extractJson } from "@hardlydifficult/ai";
112
+
113
+ extractJson('Here is the result:\n```json\n{"key": "value"}\n```');
114
+ // [{ key: "value" }]
115
+ ```
116
+
117
+ ### `extractTyped(text, schema, sentinel?)`
118
+
119
+ Extract and validate JSON against a schema. Works with any object that has a `safeParse` method.
120
+
121
+ ```typescript
122
+ import { extractTyped } from "@hardlydifficult/ai";
123
+
124
+ const Person = z.object({ name: z.string(), age: z.number() });
125
+ extractTyped('{"name": "Alice", "age": 30}', Person);
126
+ // [{ name: "Alice", age: 30 }]
127
+ ```
128
+
129
+ ### `extractCodeBlock(text, lang?)`
130
+
131
+ Extract fenced code block contents, optionally filtered by language tag.
132
+
133
+ ### `extractTextContent(content)`
134
+
135
+ Extract plain text from multimodal content (string or content array).
136
+
137
+ ### `toPlainTextMessages(messages)`
138
+
139
+ Convert multimodal messages to plain text messages.
@@ -0,0 +1,11 @@
1
+ import type { LanguageModel } from "ai";
2
+ /** Anthropic aliases — auto-resolve to the latest snapshot. */
3
+ declare const MODELS: {
4
+ readonly sonnet: "claude-sonnet-4-5";
5
+ readonly haiku: "claude-haiku-4-5";
6
+ readonly opus: "claude-opus-4-6";
7
+ };
8
+ /** Creates an Anthropic language model from a short variant name. */
9
+ export declare function claude(variant: keyof typeof MODELS): LanguageModel;
10
+ export {};
11
+ //# sourceMappingURL=claude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAExC,+DAA+D;AAC/D,QAAA,MAAM,MAAM;;;;CAIF,CAAC;AAEX,qEAAqE;AACrE,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,OAAO,MAAM,GAAG,aAAa,CAElE"}
package/dist/claude.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.claude = claude;
4
+ const anthropic_1 = require("@ai-sdk/anthropic");
5
+ /** Anthropic aliases — auto-resolve to the latest snapshot. */
6
+ const MODELS = {
7
+ sonnet: "claude-sonnet-4-5",
8
+ haiku: "claude-haiku-4-5",
9
+ opus: "claude-opus-4-6",
10
+ };
11
+ /** Creates an Anthropic language model from a short variant name. */
12
+ function claude(variant) {
13
+ return (0, anthropic_1.createAnthropic)()(MODELS[variant]);
14
+ }
15
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":";;AAWA,wBAEC;AAbD,iDAAoD;AAGpD,+DAA+D;AAC/D,MAAM,MAAM,GAAG;IACb,MAAM,EAAE,mBAAmB;IAC3B,KAAK,EAAE,kBAAkB;IACzB,IAAI,EAAE,iBAAiB;CACf,CAAC;AAEX,qEAAqE;AACrE,SAAgB,MAAM,CAAC,OAA4B;IACjD,OAAO,IAAA,2BAAe,GAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Logger } from "@hardlydifficult/logger";
2
+ import { type LanguageModel } from "ai";
3
+ import type { AI, AIOptions, AITracker } from "./types.js";
4
+ /** Creates an AI client with required usage tracking and logging. */
5
+ export declare function createAI(model: LanguageModel, tracker: AITracker, logger: Logger, options?: AIOptions): AI;
6
+ //# sourceMappingURL=createAI.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createAI.d.ts","sourceRoot":"","sources":["../src/createAI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAgB,KAAK,aAAa,EAAU,MAAM,IAAI,CAAC;AAG9D,OAAO,KAAK,EACV,EAAE,EACF,SAAS,EACT,SAAS,EAIV,MAAM,YAAY,CAAC;AA6IpB,qEAAqE;AACrE,wBAAgB,QAAQ,CACtB,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,SAAS,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,SAAS,GAClB,EAAE,CAoBJ"}
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAI = createAI;
4
+ const ai_1 = require("ai");
5
+ const DEFAULT_MAX_TOKENS = 4096;
6
+ function createChatCall(model, tracker, logger, maxTokens, messages, systemPrompt) {
7
+ let zodSchema;
8
+ async function execute() {
9
+ const promptLength = messages.reduce((n, m) => n + m.content.length, 0);
10
+ logger.debug("AI request", {
11
+ promptLength,
12
+ hasSystemPrompt: systemPrompt !== undefined,
13
+ hasSchema: zodSchema !== undefined,
14
+ });
15
+ const startMs = Date.now();
16
+ const result = await (0, ai_1.generateText)({
17
+ model,
18
+ messages,
19
+ maxOutputTokens: maxTokens,
20
+ ...(systemPrompt !== undefined && { system: systemPrompt }),
21
+ ...(zodSchema !== undefined && {
22
+ output: ai_1.Output.object({ schema: zodSchema }),
23
+ }),
24
+ });
25
+ const durationMs = Date.now() - startMs;
26
+ const resultUsage = result.usage;
27
+ const usage = {
28
+ inputTokens: resultUsage.inputTokens ?? 0,
29
+ outputTokens: resultUsage.outputTokens ?? 0,
30
+ durationMs,
31
+ };
32
+ tracker.record(usage);
33
+ logger.debug("AI response", {
34
+ responseLength: result.text.length,
35
+ durationMs,
36
+ inputTokens: usage.inputTokens,
37
+ outputTokens: usage.outputTokens,
38
+ });
39
+ const responseMessages = [
40
+ ...messages,
41
+ { role: "assistant", content: result.text },
42
+ ];
43
+ const msg = {
44
+ text: result.text,
45
+ usage,
46
+ reply(prompt) {
47
+ return createChatCall(model, tracker, logger, maxTokens, [...responseMessages, { role: "user", content: prompt }], systemPrompt);
48
+ },
49
+ };
50
+ if (zodSchema !== undefined) {
51
+ return { ...msg, data: result.output };
52
+ }
53
+ return msg;
54
+ }
55
+ const call = {
56
+ text() {
57
+ return {
58
+ then(onfulfilled, onrejected) {
59
+ return execute().then((msg) => {
60
+ return onfulfilled
61
+ ? onfulfilled(msg.text)
62
+ : msg.text;
63
+ }, onrejected);
64
+ },
65
+ };
66
+ },
67
+ zod(schema) {
68
+ zodSchema = schema;
69
+ return {
70
+ then(onfulfilled, onrejected) {
71
+ return execute().then((msg) => {
72
+ const data = msg
73
+ .data;
74
+ return onfulfilled
75
+ ? onfulfilled(data)
76
+ : data;
77
+ }, onrejected);
78
+ },
79
+ };
80
+ },
81
+ then(onfulfilled, onrejected) {
82
+ return execute().then(onfulfilled, onrejected);
83
+ },
84
+ };
85
+ return call;
86
+ }
87
+ /** Creates an AI client with required usage tracking and logging. */
88
+ function createAI(model, tracker, logger, options) {
89
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for JS callers
90
+ if (tracker === undefined || tracker === null) {
91
+ throw new Error("AITracker is required — all AI usage must be tracked");
92
+ }
93
+ const maxTokens = options?.maxTokens ?? DEFAULT_MAX_TOKENS;
94
+ return {
95
+ chat(prompt, systemPrompt) {
96
+ return createChatCall(model, tracker, logger, maxTokens, [{ role: "user", content: prompt }], systemPrompt);
97
+ },
98
+ };
99
+ }
100
+ //# sourceMappingURL=createAI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createAI.js","sourceRoot":"","sources":["../src/createAI.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Extracts the contents of fenced code blocks from a string, optionally filtered by language tag. */
2
+ export declare function extractCodeBlock(text: string, lang?: string): string[];
3
+ //# sourceMappingURL=extractCodeBlock.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractCodeBlock = extractCodeBlock;
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. */
6
+ function extractCodeBlock(text, lang) {
7
+ const results = [];
8
+ for (const match of text.matchAll(codeBlockRegex)) {
9
+ const tag = match[1];
10
+ const content = match[2];
11
+ if (lang !== undefined && tag.toLowerCase() !== lang.toLowerCase()) {
12
+ continue;
13
+ }
14
+ results.push(content.trimEnd());
15
+ }
16
+ return results;
17
+ }
18
+ //# sourceMappingURL=extractCodeBlock.js.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,3 @@
1
+ /** Extracts JSON values from text using progressive strategies: direct parse, code blocks, then balanced-brace scanning. */
2
+ export declare function extractJson(text: string, sentinel?: string): unknown[];
3
+ //# sourceMappingURL=extractJson.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractJson = extractJson;
4
+ const extractCodeBlock_js_1 = require("./extractCodeBlock.js");
5
+ const findBalanced_js_1 = require("./findBalanced.js");
6
+ function tryParse(text) {
7
+ try {
8
+ return JSON.parse(text);
9
+ }
10
+ catch {
11
+ return undefined;
12
+ }
13
+ }
14
+ /** Extracts JSON values from text using progressive strategies: direct parse, code blocks, then balanced-brace scanning. */
15
+ function extractJson(text, sentinel) {
16
+ // Sentinel check — if the text contains the sentinel, treat as "no findings"
17
+ if (sentinel !== undefined && text.includes(sentinel)) {
18
+ return [];
19
+ }
20
+ // Pass 1: try the whole text
21
+ const direct = tryParse(text.trim());
22
+ if (direct !== undefined) {
23
+ return [direct];
24
+ }
25
+ // Pass 2: code blocks — json-tagged first, then any
26
+ const fromBlocks = [];
27
+ for (const block of (0, extractCodeBlock_js_1.extractCodeBlock)(text, "json")) {
28
+ const parsed = tryParse(block.trim());
29
+ if (parsed !== undefined) {
30
+ fromBlocks.push(parsed);
31
+ }
32
+ }
33
+ if (fromBlocks.length === 0) {
34
+ for (const block of (0, extractCodeBlock_js_1.extractCodeBlock)(text)) {
35
+ const parsed = tryParse(block.trim());
36
+ if (parsed !== undefined) {
37
+ fromBlocks.push(parsed);
38
+ }
39
+ }
40
+ }
41
+ if (fromBlocks.length > 0) {
42
+ return fromBlocks;
43
+ }
44
+ // Pass 3: all balanced braces / brackets in prose
45
+ const results = [];
46
+ for (const match of (0, findBalanced_js_1.findAllBalanced)(text, "{", "}")) {
47
+ const parsed = tryParse(match);
48
+ if (parsed !== undefined) {
49
+ results.push(parsed);
50
+ }
51
+ }
52
+ for (const match of (0, findBalanced_js_1.findAllBalanced)(text, "[", "]")) {
53
+ const parsed = tryParse(match);
54
+ if (parsed !== undefined) {
55
+ results.push(parsed);
56
+ }
57
+ }
58
+ return results;
59
+ }
60
+ //# sourceMappingURL=extractJson.js.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,16 @@
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[];
16
+ //# sourceMappingURL=extractTyped.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractTyped = extractTyped;
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. */
6
+ function extractTyped(text, schema, sentinel) {
7
+ const results = (0, extractJson_js_1.extractJson)(text, sentinel);
8
+ const validated = [];
9
+ for (const json of results) {
10
+ const result = schema.safeParse(json);
11
+ if (result.success) {
12
+ validated.push(result.data);
13
+ }
14
+ }
15
+ return validated;
16
+ }
17
+ //# sourceMappingURL=extractTyped.js.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,5 @@
1
+ /** Finds the first substring enclosed by balanced open/close characters, respecting JSON string escaping. */
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. */
4
+ export declare function findAllBalanced(text: string, openChar: string, closeChar: string): string[];
5
+ //# sourceMappingURL=findBalanced.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findBalanced = findBalanced;
4
+ exports.findAllBalanced = findAllBalanced;
5
+ /** Finds the first substring enclosed by balanced open/close characters, respecting JSON string escaping. */
6
+ function findBalanced(text, openChar, closeChar) {
7
+ const start = text.indexOf(openChar);
8
+ if (start === -1) {
9
+ return null;
10
+ }
11
+ let depth = 0;
12
+ let inString = false;
13
+ let escaped = false;
14
+ for (let i = start; i < text.length; i++) {
15
+ const ch = text[i];
16
+ if (escaped) {
17
+ escaped = false;
18
+ continue;
19
+ }
20
+ if (ch === "\\") {
21
+ if (inString) {
22
+ escaped = true;
23
+ }
24
+ continue;
25
+ }
26
+ if (ch === '"') {
27
+ inString = !inString;
28
+ continue;
29
+ }
30
+ if (inString) {
31
+ continue;
32
+ }
33
+ if (ch === openChar) {
34
+ depth++;
35
+ }
36
+ else if (ch === closeChar) {
37
+ depth--;
38
+ if (depth === 0) {
39
+ return text.slice(start, i + 1);
40
+ }
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ /** Finds all non-overlapping substrings enclosed by balanced open/close characters. */
46
+ function findAllBalanced(text, openChar, closeChar) {
47
+ const results = [];
48
+ let offset = 0;
49
+ while (offset < text.length) {
50
+ const remaining = text.slice(offset);
51
+ const match = findBalanced(remaining, openChar, closeChar);
52
+ if (match === null) {
53
+ break;
54
+ }
55
+ results.push(match);
56
+ const matchStart = remaining.indexOf(openChar);
57
+ offset += matchStart + match.length;
58
+ }
59
+ return results;
60
+ }
61
+ //# sourceMappingURL=findBalanced.js.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,9 @@
1
+ export { createAI } from "./createAI.js";
2
+ export { claude } from "./claude.js";
3
+ export { ollama } from "./ollama.js";
4
+ export type { AI, AIOptions, AITracker, ChatCall, ChatMessage, Usage, } from "./types.js";
5
+ export { extractCodeBlock } from "./extractCodeBlock.js";
6
+ export { extractJson } from "./extractJson.js";
7
+ export { extractTyped, type SchemaLike } from "./extractTyped.js";
8
+ export { extractTextContent, toPlainTextMessages, type MultimodalMessage, } from "./multimodal.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EACV,EAAE,EACF,SAAS,EACT,SAAS,EACT,QAAQ,EACR,WAAW,EACX,KAAK,GACN,MAAM,YAAY,CAAC;AACpB,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 ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toPlainTextMessages = exports.extractTextContent = exports.extractTyped = exports.extractJson = exports.extractCodeBlock = exports.ollama = exports.claude = exports.createAI = void 0;
4
+ var createAI_js_1 = require("./createAI.js");
5
+ Object.defineProperty(exports, "createAI", { enumerable: true, get: function () { return createAI_js_1.createAI; } });
6
+ var claude_js_1 = require("./claude.js");
7
+ Object.defineProperty(exports, "claude", { enumerable: true, get: function () { return claude_js_1.claude; } });
8
+ var ollama_js_1 = require("./ollama.js");
9
+ Object.defineProperty(exports, "ollama", { enumerable: true, get: function () { return ollama_js_1.ollama; } });
10
+ var extractCodeBlock_js_1 = require("./extractCodeBlock.js");
11
+ Object.defineProperty(exports, "extractCodeBlock", { enumerable: true, get: function () { return extractCodeBlock_js_1.extractCodeBlock; } });
12
+ var extractJson_js_1 = require("./extractJson.js");
13
+ Object.defineProperty(exports, "extractJson", { enumerable: true, get: function () { return extractJson_js_1.extractJson; } });
14
+ var extractTyped_js_1 = require("./extractTyped.js");
15
+ Object.defineProperty(exports, "extractTyped", { enumerable: true, get: function () { return extractTyped_js_1.extractTyped; } });
16
+ var multimodal_js_1 = require("./multimodal.js");
17
+ Object.defineProperty(exports, "extractTextContent", { enumerable: true, get: function () { return multimodal_js_1.extractTextContent; } });
18
+ Object.defineProperty(exports, "toPlainTextMessages", { enumerable: true, get: function () { return multimodal_js_1.toPlainTextMessages; } });
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6CAAyC;AAAhC,uGAAA,QAAQ,OAAA;AACjB,yCAAqC;AAA5B,mGAAA,MAAM,OAAA;AACf,yCAAqC;AAA5B,mGAAA,MAAM,OAAA;AASf,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"}
@@ -0,0 +1,4 @@
1
+ import type { LanguageModel } from "ai";
2
+ /** Creates an Ollama language model. Model names match whatever is installed locally. */
3
+ export declare function ollama(model: string): LanguageModel;
4
+ //# sourceMappingURL=ollama.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.d.ts","sourceRoot":"","sources":["../src/ollama.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAGxC,yFAAyF;AACzF,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAEnD"}
package/dist/ollama.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ollama = ollama;
4
+ const ollama_ai_provider_v2_1 = require("ollama-ai-provider-v2");
5
+ /** Creates an Ollama language model. Model names match whatever is installed locally. */
6
+ function ollama(model) {
7
+ return (0, ollama_ai_provider_v2_1.createOllama)()(model);
8
+ }
9
+ //# sourceMappingURL=ollama.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../src/ollama.ts"],"names":[],"mappings":";;AAIA,wBAEC;AALD,iEAAqD;AAErD,yFAAyF;AACzF,SAAgB,MAAM,CAAC,KAAa;IAClC,OAAO,IAAA,oCAAY,GAAE,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { z } from "zod";
2
+ export interface Usage {
3
+ inputTokens: number;
4
+ outputTokens: number;
5
+ durationMs: number;
6
+ }
7
+ export interface AITracker {
8
+ record(usage: Usage): void;
9
+ }
10
+ export interface AIOptions {
11
+ maxTokens?: number;
12
+ }
13
+ export interface ChatMessage {
14
+ text: string;
15
+ usage: Usage;
16
+ reply(prompt: string): ChatCall;
17
+ }
18
+ export interface ChatCall extends PromiseLike<ChatMessage> {
19
+ text(): PromiseLike<string>;
20
+ zod<TSchema extends z.ZodType>(schema: TSchema): PromiseLike<z.infer<TSchema>>;
21
+ }
22
+ export interface AI {
23
+ chat(prompt: string, systemPrompt?: string): ChatCall;
24
+ }
25
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,MAAM,WAAW,KAAK;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;CACjC;AAED,MAAM,WAAW,QAAS,SAAQ,WAAW,CAAC,WAAW,CAAC;IACxD,IAAI,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5B,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO,EAC3B,MAAM,EAAE,OAAO,GACd,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,EAAE;IACjB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;CACvD"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@hardlydifficult/ai",
3
+ "version": "1.0.0",
4
+ "main": "./dist/index.js",
5
+ "types": "./dist/index.d.ts",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "test": "vitest run",
12
+ "test:watch": "vitest",
13
+ "test:coverage": "vitest run --coverage",
14
+ "lint": "tsc --noEmit",
15
+ "clean": "rm -rf dist"
16
+ },
17
+ "dependencies": {
18
+ "@ai-sdk/anthropic": "3.0.40",
19
+ "@hardlydifficult/logger": "file:../logger",
20
+ "ai": "6.0.78",
21
+ "ollama-ai-provider-v2": "3.0.4",
22
+ "zod": "4.3.6"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "20.19.31",
26
+ "typescript": "5.8.3",
27
+ "vitest": "1.6.1"
28
+ },
29
+ "peerDependencies": {
30
+ "@hardlydifficult/logger": ">=1.0.0"
31
+ },
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ }
35
+ }