@bpmnkit/casen-worker-ai 0.1.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 (70) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-check.log +5 -0
  3. package/.turbo/turbo-test.log +18 -0
  4. package/.turbo/turbo-typecheck.log +4 -0
  5. package/CHANGELOG.md +7 -0
  6. package/LICENSE +21 -0
  7. package/README.md +177 -0
  8. package/dist/config.d.ts +13 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +19 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/config.test.d.ts +2 -0
  13. package/dist/config.test.d.ts.map +1 -0
  14. package/dist/config.test.js +35 -0
  15. package/dist/config.test.js.map +1 -0
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +53 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/llm.d.ts +18 -0
  21. package/dist/llm.d.ts.map +1 -0
  22. package/dist/llm.js +57 -0
  23. package/dist/llm.js.map +1 -0
  24. package/dist/operations/classify.d.ts +16 -0
  25. package/dist/operations/classify.d.ts.map +1 -0
  26. package/dist/operations/classify.js +74 -0
  27. package/dist/operations/classify.js.map +1 -0
  28. package/dist/operations/classify.test.d.ts +2 -0
  29. package/dist/operations/classify.test.d.ts.map +1 -0
  30. package/dist/operations/classify.test.js +74 -0
  31. package/dist/operations/classify.test.js.map +1 -0
  32. package/dist/operations/decide.d.ts +16 -0
  33. package/dist/operations/decide.d.ts.map +1 -0
  34. package/dist/operations/decide.js +60 -0
  35. package/dist/operations/decide.js.map +1 -0
  36. package/dist/operations/decide.test.d.ts +2 -0
  37. package/dist/operations/decide.test.d.ts.map +1 -0
  38. package/dist/operations/decide.test.js +65 -0
  39. package/dist/operations/decide.test.js.map +1 -0
  40. package/dist/operations/extract.d.ts +16 -0
  41. package/dist/operations/extract.d.ts.map +1 -0
  42. package/dist/operations/extract.js +70 -0
  43. package/dist/operations/extract.js.map +1 -0
  44. package/dist/operations/extract.test.d.ts +2 -0
  45. package/dist/operations/extract.test.d.ts.map +1 -0
  46. package/dist/operations/extract.test.js +65 -0
  47. package/dist/operations/extract.test.js.map +1 -0
  48. package/dist/operations/summarize.d.ts +16 -0
  49. package/dist/operations/summarize.d.ts.map +1 -0
  50. package/dist/operations/summarize.js +59 -0
  51. package/dist/operations/summarize.js.map +1 -0
  52. package/dist/operations/summarize.test.d.ts +2 -0
  53. package/dist/operations/summarize.test.d.ts.map +1 -0
  54. package/dist/operations/summarize.test.js +42 -0
  55. package/dist/operations/summarize.test.js.map +1 -0
  56. package/package.json +69 -0
  57. package/src/config.test.ts +40 -0
  58. package/src/config.ts +27 -0
  59. package/src/index.ts +54 -0
  60. package/src/llm.ts +67 -0
  61. package/src/operations/classify.test.ts +101 -0
  62. package/src/operations/classify.ts +85 -0
  63. package/src/operations/decide.test.ts +82 -0
  64. package/src/operations/decide.ts +70 -0
  65. package/src/operations/extract.test.ts +80 -0
  66. package/src/operations/extract.ts +80 -0
  67. package/src/operations/summarize.test.ts +48 -0
  68. package/src/operations/summarize.ts +68 -0
  69. package/tsconfig.json +10 -0
  70. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,80 @@
1
+ import type { WorkerJob, WorkerJobResult } from "@bpmnkit/cli-sdk"
2
+ import type { AiWorkerConfig } from "../config.js"
3
+ import { RetryableError, callLlm, parseJsonResponse } from "../llm.js"
4
+
5
+ interface ExtractResponse {
6
+ extracted: Record<string, unknown>
7
+ missingFields: string[]
8
+ }
9
+
10
+ const SYSTEM_PROMPT = `You are a structured data extraction assistant.
11
+ The user will provide unstructured text and a list of fields to extract.
12
+ Respond ONLY with valid JSON matching this exact shape:
13
+ { "extracted": { "<field>": <value or null>, ... }, "missingFields": ["<fields not found>"] }
14
+ Use null for fields you cannot confidently determine. List those field names in missingFields.
15
+ Do not include any prose outside the JSON object.`
16
+
17
+ /**
18
+ * Extracts structured fields from job.variables.input.
19
+ *
20
+ * Required job variables:
21
+ * - input: string — the unstructured text
22
+ * - fields: string[] — field names to extract
23
+ *
24
+ * Optional:
25
+ * - schema: Record<string, string> — type hints per field, e.g. { amount: "number", date: "ISO 8601 string" }
26
+ *
27
+ * Output variables: extracted (object), missingFields (array), aiModel, processedAt
28
+ */
29
+ export async function extract(job: WorkerJob, config: AiWorkerConfig): Promise<WorkerJobResult> {
30
+ const input = String(job.variables.input ?? "")
31
+ const fields = job.variables.fields
32
+ if (!Array.isArray(fields) || fields.length === 0) {
33
+ return {
34
+ outcome: "error",
35
+ errorCode: "AI_INVALID_INPUT",
36
+ errorMessage: 'Job variable "fields" must be a non-empty array of field name strings.',
37
+ }
38
+ }
39
+ const schema = job.variables.schema
40
+ const schemaHint =
41
+ schema && typeof schema === "object" && !Array.isArray(schema)
42
+ ? `\nType hints: ${JSON.stringify(schema)}`
43
+ : ""
44
+ const userMessage = `Fields to extract: ${JSON.stringify(fields)}${schemaHint}\n\nText:\n${input}`
45
+
46
+ let raw: string
47
+ try {
48
+ raw = await callLlm(SYSTEM_PROMPT, userMessage, config)
49
+ } catch (err) {
50
+ if (err instanceof RetryableError) {
51
+ return { outcome: "fail", errorMessage: err.message, retries: 2, retryBackOff: 30_000 }
52
+ }
53
+ return {
54
+ outcome: "error",
55
+ errorCode: "AI_API_ERROR",
56
+ errorMessage: err instanceof Error ? err.message : String(err),
57
+ }
58
+ }
59
+
60
+ let parsed: ExtractResponse
61
+ try {
62
+ parsed = parseJsonResponse<ExtractResponse>(raw)
63
+ } catch {
64
+ return {
65
+ outcome: "error",
66
+ errorCode: "AI_PARSE_ERROR",
67
+ errorMessage: `Model returned non-JSON response: ${raw.slice(0, 200)}`,
68
+ }
69
+ }
70
+
71
+ return {
72
+ outcome: "complete",
73
+ variables: {
74
+ extracted: parsed.extracted,
75
+ missingFields: parsed.missingFields ?? [],
76
+ aiModel: config.model,
77
+ processedAt: new Date().toISOString(),
78
+ },
79
+ }
80
+ }
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it, vi } from "vitest"
2
+ import type { AiWorkerConfig } from "../config.js"
3
+ import * as llmModule from "../llm.js"
4
+ import { summarize } from "./summarize.js"
5
+
6
+ const cfg: AiWorkerConfig = {
7
+ apiKey: "test",
8
+ model: "claude-test",
9
+ maxTokens: 512,
10
+ timeoutMs: 5000,
11
+ }
12
+
13
+ function makeJob(variables: Record<string, unknown>) {
14
+ return {
15
+ jobKey: "1",
16
+ processDefinitionId: "p1",
17
+ elementId: "e1",
18
+ processInstanceKey: "i1",
19
+ variables,
20
+ }
21
+ }
22
+
23
+ describe("summarize", () => {
24
+ it("returns complete with summary and wordCount", async () => {
25
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue(
26
+ JSON.stringify({ summary: "A short summary.", wordCount: 3 }),
27
+ )
28
+ const result = await summarize(makeJob({ input: "Long text here..." }), cfg)
29
+ expect(result.outcome).toBe("complete")
30
+ if (result.outcome === "complete") {
31
+ expect(result.variables.summary).toBe("A short summary.")
32
+ expect(result.variables.wordCount).toBe(3)
33
+ }
34
+ })
35
+
36
+ it("returns error on parse failure", async () => {
37
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue("Here is the summary: blah blah.")
38
+ const result = await summarize(makeJob({ input: "text" }), cfg)
39
+ expect(result.outcome).toBe("error")
40
+ if (result.outcome === "error") expect(result.errorCode).toBe("AI_PARSE_ERROR")
41
+ })
42
+
43
+ it("returns fail on RetryableError", async () => {
44
+ vi.spyOn(llmModule, "callLlm").mockRejectedValue(new llmModule.RetryableError("timeout"))
45
+ const result = await summarize(makeJob({ input: "text" }), cfg)
46
+ expect(result.outcome).toBe("fail")
47
+ })
48
+ })
@@ -0,0 +1,68 @@
1
+ import type { WorkerJob, WorkerJobResult } from "@bpmnkit/cli-sdk"
2
+ import type { AiWorkerConfig } from "../config.js"
3
+ import { RetryableError, callLlm, parseJsonResponse } from "../llm.js"
4
+
5
+ interface SummarizeResponse {
6
+ summary: string
7
+ wordCount: number
8
+ }
9
+
10
+ const SYSTEM_PROMPT = `You are a text summarization assistant.
11
+ The user will provide text to summarize along with a target length and style.
12
+ Respond ONLY with valid JSON matching this exact shape:
13
+ { "summary": "<the summary text>", "wordCount": <integer> }
14
+ Do not include any prose outside the JSON object.`
15
+
16
+ /**
17
+ * Summarizes job.variables.input.
18
+ *
19
+ * Required job variables:
20
+ * - input: string — the text to summarize
21
+ *
22
+ * Optional:
23
+ * - maxWords: number — target word count (default: 100)
24
+ * - style: "bullet" | "paragraph" — output style (default: "paragraph")
25
+ *
26
+ * Output variables: summary, wordCount, aiModel, processedAt
27
+ */
28
+ export async function summarize(job: WorkerJob, config: AiWorkerConfig): Promise<WorkerJobResult> {
29
+ const input = String(job.variables.input ?? "")
30
+ const maxWords = Number(job.variables.maxWords ?? 100)
31
+ const style = String(job.variables.style ?? "paragraph")
32
+ const userMessage = `Summarize the following text in ${style} style, targeting ${maxWords} words:\n\n${input}`
33
+
34
+ let raw: string
35
+ try {
36
+ raw = await callLlm(SYSTEM_PROMPT, userMessage, config)
37
+ } catch (err) {
38
+ if (err instanceof RetryableError) {
39
+ return { outcome: "fail", errorMessage: err.message, retries: 2, retryBackOff: 30_000 }
40
+ }
41
+ return {
42
+ outcome: "error",
43
+ errorCode: "AI_API_ERROR",
44
+ errorMessage: err instanceof Error ? err.message : String(err),
45
+ }
46
+ }
47
+
48
+ let parsed: SummarizeResponse
49
+ try {
50
+ parsed = parseJsonResponse<SummarizeResponse>(raw)
51
+ } catch {
52
+ return {
53
+ outcome: "error",
54
+ errorCode: "AI_PARSE_ERROR",
55
+ errorMessage: `Model returned non-JSON response: ${raw.slice(0, 200)}`,
56
+ }
57
+ }
58
+
59
+ return {
60
+ outcome: "complete",
61
+ variables: {
62
+ summary: parsed.summary,
63
+ wordCount: parsed.wordCount,
64
+ aiModel: config.model,
65
+ processedAt: new Date().toISOString(),
66
+ },
67
+ }
68
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+ "outDir": "dist",
6
+ "rootDir": "src",
7
+ "types": ["node"]
8
+ },
9
+ "include": ["src"]
10
+ }