@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,74 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import * as llmModule from "../llm.js";
3
+ import { classify } from "./classify.js";
4
+ const cfg = {
5
+ apiKey: "test",
6
+ model: "claude-test",
7
+ maxTokens: 512,
8
+ timeoutMs: 5000,
9
+ };
10
+ function makeJob(variables) {
11
+ return {
12
+ jobKey: "1",
13
+ processDefinitionId: "p1",
14
+ elementId: "e1",
15
+ processInstanceKey: "i1",
16
+ variables,
17
+ };
18
+ }
19
+ describe("classify", () => {
20
+ it("returns complete with valid category", async () => {
21
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue(JSON.stringify({ category: "billing", confidence: 0.9, rationale: "mentions invoice" }));
22
+ const result = await classify(makeJob({ input: "I need help with my invoice", categories: ["billing", "technical"] }), cfg);
23
+ expect(result.outcome).toBe("complete");
24
+ if (result.outcome === "complete") {
25
+ expect(result.variables.category).toBe("billing");
26
+ expect(result.variables.confidence).toBe(0.9);
27
+ }
28
+ });
29
+ it("returns error when model returns category outside allowed list", async () => {
30
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue(JSON.stringify({ category: "other", confidence: 0.5, rationale: "unsure" }));
31
+ const result = await classify(makeJob({ input: "anything", categories: ["billing", "technical"] }), cfg);
32
+ expect(result.outcome).toBe("error");
33
+ if (result.outcome === "error") {
34
+ expect(result.errorCode).toBe("AI_INVALID_CATEGORY");
35
+ }
36
+ });
37
+ it("returns error when model returns non-JSON", async () => {
38
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue("Sorry, I cannot classify this.");
39
+ const result = await classify(makeJob({ input: "anything", categories: ["billing", "technical"] }), cfg);
40
+ expect(result.outcome).toBe("error");
41
+ if (result.outcome === "error") {
42
+ expect(result.errorCode).toBe("AI_PARSE_ERROR");
43
+ }
44
+ });
45
+ it("returns fail on RetryableError", async () => {
46
+ vi.spyOn(llmModule, "callLlm").mockRejectedValue(new llmModule.RetryableError("rate limited"));
47
+ const result = await classify(makeJob({ input: "anything", categories: ["billing"] }), cfg);
48
+ expect(result.outcome).toBe("fail");
49
+ if (result.outcome === "fail") {
50
+ expect(result.retryBackOff).toBe(30_000);
51
+ }
52
+ });
53
+ it("returns error on hard API failure", async () => {
54
+ vi.spyOn(llmModule, "callLlm").mockRejectedValue(new Error("invalid api key"));
55
+ const result = await classify(makeJob({ input: "anything", categories: ["billing"] }), cfg);
56
+ expect(result.outcome).toBe("error");
57
+ if (result.outcome === "error") {
58
+ expect(result.errorCode).toBe("AI_API_ERROR");
59
+ }
60
+ });
61
+ it("returns error when categories variable is missing", async () => {
62
+ const result = await classify(makeJob({ input: "text" }), cfg);
63
+ expect(result.outcome).toBe("error");
64
+ if (result.outcome === "error") {
65
+ expect(result.errorCode).toBe("AI_INVALID_INPUT");
66
+ }
67
+ });
68
+ it("handles markdown code-fence wrapped JSON", async () => {
69
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue(`\`\`\`json\n${JSON.stringify({ category: "billing", confidence: 0.8, rationale: "invoice" })}\n\`\`\``);
70
+ const result = await classify(makeJob({ input: "invoice question", categories: ["billing", "technical"] }), cfg);
71
+ expect(result.outcome).toBe("complete");
72
+ });
73
+ });
74
+ //# sourceMappingURL=classify.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.test.js","sourceRoot":"","sources":["../../src/operations/classify.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { WorkerJob, WorkerJobResult } from "@bpmnkit/cli-sdk";
2
+ import type { AiWorkerConfig } from "../config.js";
3
+ /**
4
+ * Makes a boolean decision for job.variables.question based on context and an optional policy.
5
+ *
6
+ * Required job variables:
7
+ * - question: string — the yes/no question to answer
8
+ * - context: string — relevant facts for the decision
9
+ *
10
+ * Optional:
11
+ * - policy: string — natural language policy text the model must apply
12
+ *
13
+ * Output variables: decision (boolean), rationale, confidence, aiModel, processedAt
14
+ */
15
+ export declare function decide(job: WorkerJob, config: AiWorkerConfig): Promise<WorkerJobResult>;
16
+ //# sourceMappingURL=decide.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decide.d.ts","sourceRoot":"","sources":["../../src/operations/decide.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAelD;;;;;;;;;;;GAWG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAyC7F"}
@@ -0,0 +1,60 @@
1
+ import { RetryableError, callLlm, parseJsonResponse } from "../llm.js";
2
+ const SYSTEM_PROMPT = `You are a decision-making assistant.
3
+ The user will provide a yes/no question, relevant context, and optionally a policy to apply.
4
+ Respond ONLY with valid JSON matching this exact shape:
5
+ { "decision": <true|false>, "rationale": "<one or two sentences>", "confidence": <0.0–1.0> }
6
+ Apply the policy strictly if provided. Do not include any prose outside the JSON object.`;
7
+ /**
8
+ * Makes a boolean decision for job.variables.question based on context and an optional policy.
9
+ *
10
+ * Required job variables:
11
+ * - question: string — the yes/no question to answer
12
+ * - context: string — relevant facts for the decision
13
+ *
14
+ * Optional:
15
+ * - policy: string — natural language policy text the model must apply
16
+ *
17
+ * Output variables: decision (boolean), rationale, confidence, aiModel, processedAt
18
+ */
19
+ export async function decide(job, config) {
20
+ const question = String(job.variables.question ?? "");
21
+ const context = String(job.variables.context ?? "");
22
+ const policy = job.variables.policy ? `\nPolicy to apply:\n${String(job.variables.policy)}` : "";
23
+ const userMessage = `Question: ${question}\n\nContext:\n${context}${policy}`;
24
+ let raw;
25
+ try {
26
+ raw = await callLlm(SYSTEM_PROMPT, userMessage, config);
27
+ }
28
+ catch (err) {
29
+ if (err instanceof RetryableError) {
30
+ return { outcome: "fail", errorMessage: err.message, retries: 2, retryBackOff: 30_000 };
31
+ }
32
+ return {
33
+ outcome: "error",
34
+ errorCode: "AI_API_ERROR",
35
+ errorMessage: err instanceof Error ? err.message : String(err),
36
+ };
37
+ }
38
+ let parsed;
39
+ try {
40
+ parsed = parseJsonResponse(raw);
41
+ }
42
+ catch {
43
+ return {
44
+ outcome: "error",
45
+ errorCode: "AI_PARSE_ERROR",
46
+ errorMessage: `Model returned non-JSON response: ${raw.slice(0, 200)}`,
47
+ };
48
+ }
49
+ return {
50
+ outcome: "complete",
51
+ variables: {
52
+ decision: parsed.decision,
53
+ rationale: parsed.rationale,
54
+ confidence: parsed.confidence,
55
+ aiModel: config.model,
56
+ processedAt: new Date().toISOString(),
57
+ },
58
+ };
59
+ }
60
+ //# sourceMappingURL=decide.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decide.js","sourceRoot":"","sources":["../../src/operations/decide.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAQtE,MAAM,aAAa,GAAG;;;;yFAImE,CAAA;AAEzF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,GAAc,EAAE,MAAsB;IAClE,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAA;IACrD,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAAA;IACnD,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,uBAAuB,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAChG,MAAM,WAAW,GAAG,aAAa,QAAQ,iBAAiB,OAAO,GAAG,MAAM,EAAE,CAAA;IAE5E,IAAI,GAAW,CAAA;IACf,IAAI,CAAC;QACJ,GAAG,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,WAAW,EAAE,MAAM,CAAC,CAAA;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;YACnC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAA;QACxF,CAAC;QACD,OAAO;YACN,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,cAAc;YACzB,YAAY,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SAC9D,CAAA;IACF,CAAC;IAED,IAAI,MAAsB,CAAA;IAC1B,IAAI,CAAC;QACJ,MAAM,GAAG,iBAAiB,CAAiB,GAAG,CAAC,CAAA;IAChD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO;YACN,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,gBAAgB;YAC3B,YAAY,EAAE,qCAAqC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;SACtE,CAAA;IACF,CAAC;IAED,OAAO;QACN,OAAO,EAAE,UAAU;QACnB,SAAS,EAAE;YACV,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,OAAO,EAAE,MAAM,CAAC,KAAK;YACrB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACrC;KACD,CAAA;AACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=decide.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decide.test.d.ts","sourceRoot":"","sources":["../../src/operations/decide.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import * as llmModule from "../llm.js";
3
+ import { decide } from "./decide.js";
4
+ const cfg = {
5
+ apiKey: "test",
6
+ model: "claude-test",
7
+ maxTokens: 512,
8
+ timeoutMs: 5000,
9
+ };
10
+ function makeJob(variables) {
11
+ return {
12
+ jobKey: "1",
13
+ processDefinitionId: "p1",
14
+ elementId: "e1",
15
+ processInstanceKey: "i1",
16
+ variables,
17
+ };
18
+ }
19
+ describe("decide", () => {
20
+ it("returns complete with decision=true", async () => {
21
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue(JSON.stringify({ decision: true, rationale: "Meets all criteria.", confidence: 0.92 }));
22
+ const result = await decide(makeJob({ question: "Should we approve?", context: "Score 720, amount 30000" }), cfg);
23
+ expect(result.outcome).toBe("complete");
24
+ if (result.outcome === "complete") {
25
+ expect(result.variables.decision).toBe(true);
26
+ expect(result.variables.confidence).toBe(0.92);
27
+ }
28
+ });
29
+ it("returns complete with decision=false", async () => {
30
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue(JSON.stringify({ decision: false, rationale: "Exceeds threshold.", confidence: 0.87 }));
31
+ const result = await decide(makeJob({ question: "Approve?", context: "debt ratio 55%" }), cfg);
32
+ expect(result.outcome).toBe("complete");
33
+ if (result.outcome === "complete") {
34
+ expect(result.variables.decision).toBe(false);
35
+ }
36
+ });
37
+ it("returns error on parse failure", async () => {
38
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue("I recommend approval based on the data.");
39
+ const result = await decide(makeJob({ question: "Approve?", context: "context" }), cfg);
40
+ expect(result.outcome).toBe("error");
41
+ if (result.outcome === "error")
42
+ expect(result.errorCode).toBe("AI_PARSE_ERROR");
43
+ });
44
+ it("returns fail on RetryableError", async () => {
45
+ vi.spyOn(llmModule, "callLlm").mockRejectedValue(new llmModule.RetryableError("503"));
46
+ const result = await decide(makeJob({ question: "Approve?", context: "context" }), cfg);
47
+ expect(result.outcome).toBe("fail");
48
+ if (result.outcome === "fail")
49
+ expect(result.retries).toBe(2);
50
+ });
51
+ it("includes policy in prompt when provided", async () => {
52
+ const spy = vi
53
+ .spyOn(llmModule, "callLlm")
54
+ .mockResolvedValue(JSON.stringify({ decision: false, rationale: "Violates policy.", confidence: 0.95 }));
55
+ await decide(makeJob({
56
+ question: "Approve?",
57
+ context: "score 650",
58
+ policy: "Minimum score is 680.",
59
+ }), cfg);
60
+ const call = spy.mock.calls[0];
61
+ const [, userMessage] = call ?? [];
62
+ expect(userMessage).toContain("Minimum score is 680.");
63
+ });
64
+ });
65
+ //# sourceMappingURL=decide.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decide.test.js","sourceRoot":"","sources":["../../src/operations/decide.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { WorkerJob, WorkerJobResult } from "@bpmnkit/cli-sdk";
2
+ import type { AiWorkerConfig } from "../config.js";
3
+ /**
4
+ * Extracts structured fields from job.variables.input.
5
+ *
6
+ * Required job variables:
7
+ * - input: string — the unstructured text
8
+ * - fields: string[] — field names to extract
9
+ *
10
+ * Optional:
11
+ * - schema: Record<string, string> — type hints per field, e.g. { amount: "number", date: "ISO 8601 string" }
12
+ *
13
+ * Output variables: extracted (object), missingFields (array), aiModel, processedAt
14
+ */
15
+ export declare function extract(job: WorkerJob, config: AiWorkerConfig): Promise<WorkerJobResult>;
16
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/operations/extract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAelD;;;;;;;;;;;GAWG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAmD9F"}
@@ -0,0 +1,70 @@
1
+ import { RetryableError, callLlm, parseJsonResponse } from "../llm.js";
2
+ const SYSTEM_PROMPT = `You are a structured data extraction assistant.
3
+ The user will provide unstructured text and a list of fields to extract.
4
+ Respond ONLY with valid JSON matching this exact shape:
5
+ { "extracted": { "<field>": <value or null>, ... }, "missingFields": ["<fields not found>"] }
6
+ Use null for fields you cannot confidently determine. List those field names in missingFields.
7
+ Do not include any prose outside the JSON object.`;
8
+ /**
9
+ * Extracts structured fields from job.variables.input.
10
+ *
11
+ * Required job variables:
12
+ * - input: string — the unstructured text
13
+ * - fields: string[] — field names to extract
14
+ *
15
+ * Optional:
16
+ * - schema: Record<string, string> — type hints per field, e.g. { amount: "number", date: "ISO 8601 string" }
17
+ *
18
+ * Output variables: extracted (object), missingFields (array), aiModel, processedAt
19
+ */
20
+ export async function extract(job, config) {
21
+ const input = String(job.variables.input ?? "");
22
+ const fields = job.variables.fields;
23
+ if (!Array.isArray(fields) || fields.length === 0) {
24
+ return {
25
+ outcome: "error",
26
+ errorCode: "AI_INVALID_INPUT",
27
+ errorMessage: 'Job variable "fields" must be a non-empty array of field name strings.',
28
+ };
29
+ }
30
+ const schema = job.variables.schema;
31
+ const schemaHint = schema && typeof schema === "object" && !Array.isArray(schema)
32
+ ? `\nType hints: ${JSON.stringify(schema)}`
33
+ : "";
34
+ const userMessage = `Fields to extract: ${JSON.stringify(fields)}${schemaHint}\n\nText:\n${input}`;
35
+ let raw;
36
+ try {
37
+ raw = await callLlm(SYSTEM_PROMPT, userMessage, config);
38
+ }
39
+ catch (err) {
40
+ if (err instanceof RetryableError) {
41
+ return { outcome: "fail", errorMessage: err.message, retries: 2, retryBackOff: 30_000 };
42
+ }
43
+ return {
44
+ outcome: "error",
45
+ errorCode: "AI_API_ERROR",
46
+ errorMessage: err instanceof Error ? err.message : String(err),
47
+ };
48
+ }
49
+ let parsed;
50
+ try {
51
+ parsed = parseJsonResponse(raw);
52
+ }
53
+ catch {
54
+ return {
55
+ outcome: "error",
56
+ errorCode: "AI_PARSE_ERROR",
57
+ errorMessage: `Model returned non-JSON response: ${raw.slice(0, 200)}`,
58
+ };
59
+ }
60
+ return {
61
+ outcome: "complete",
62
+ variables: {
63
+ extracted: parsed.extracted,
64
+ missingFields: parsed.missingFields ?? [],
65
+ aiModel: config.model,
66
+ processedAt: new Date().toISOString(),
67
+ },
68
+ };
69
+ }
70
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/operations/extract.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAOtE,MAAM,aAAa,GAAG;;;;;kDAK4B,CAAA;AAElD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,GAAc,EAAE,MAAsB;IACnE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;IAC/C,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC,MAAM,CAAA;IACnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnD,OAAO;YACN,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,kBAAkB;YAC7B,YAAY,EAAE,wEAAwE;SACtF,CAAA;IACF,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC,MAAM,CAAA;IACnC,MAAM,UAAU,GACf,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAC7D,CAAC,CAAC,iBAAiB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE;QAC3C,CAAC,CAAC,EAAE,CAAA;IACN,MAAM,WAAW,GAAG,sBAAsB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,UAAU,cAAc,KAAK,EAAE,CAAA;IAElG,IAAI,GAAW,CAAA;IACf,IAAI,CAAC;QACJ,GAAG,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,WAAW,EAAE,MAAM,CAAC,CAAA;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;YACnC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAA;QACxF,CAAC;QACD,OAAO;YACN,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,cAAc;YACzB,YAAY,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SAC9D,CAAA;IACF,CAAC;IAED,IAAI,MAAuB,CAAA;IAC3B,IAAI,CAAC;QACJ,MAAM,GAAG,iBAAiB,CAAkB,GAAG,CAAC,CAAA;IACjD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO;YACN,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,gBAAgB;YAC3B,YAAY,EAAE,qCAAqC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;SACtE,CAAA;IACF,CAAC;IAED,OAAO;QACN,OAAO,EAAE,UAAU;QACnB,SAAS,EAAE;YACV,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,EAAE;YACzC,OAAO,EAAE,MAAM,CAAC,KAAK;YACrB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACrC;KACD,CAAA;AACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=extract.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.test.d.ts","sourceRoot":"","sources":["../../src/operations/extract.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import * as llmModule from "../llm.js";
3
+ import { extract } from "./extract.js";
4
+ const cfg = {
5
+ apiKey: "test",
6
+ model: "claude-test",
7
+ maxTokens: 512,
8
+ timeoutMs: 5000,
9
+ };
10
+ function makeJob(variables) {
11
+ return {
12
+ jobKey: "1",
13
+ processDefinitionId: "p1",
14
+ elementId: "e1",
15
+ processInstanceKey: "i1",
16
+ variables,
17
+ };
18
+ }
19
+ describe("extract", () => {
20
+ it("returns complete with extracted fields and empty missingFields", async () => {
21
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue(JSON.stringify({
22
+ extracted: { vendorName: "Acme Corp", totalAmount: 1250.0 },
23
+ missingFields: [],
24
+ }));
25
+ const result = await extract(makeJob({
26
+ input: "Invoice from Acme Corp, total $1250",
27
+ fields: ["vendorName", "totalAmount"],
28
+ }), cfg);
29
+ expect(result.outcome).toBe("complete");
30
+ if (result.outcome === "complete") {
31
+ expect(result.variables.extracted).toEqual({ vendorName: "Acme Corp", totalAmount: 1250.0 });
32
+ expect(result.variables.missingFields).toEqual([]);
33
+ }
34
+ });
35
+ it("returns complete even when some fields are missing", async () => {
36
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue(JSON.stringify({
37
+ extracted: { vendorName: "Acme Corp", invoiceDate: null },
38
+ missingFields: ["invoiceDate"],
39
+ }));
40
+ const result = await extract(makeJob({ input: "Invoice from Acme Corp", fields: ["vendorName", "invoiceDate"] }), cfg);
41
+ expect(result.outcome).toBe("complete");
42
+ if (result.outcome === "complete") {
43
+ expect(result.variables.missingFields).toEqual(["invoiceDate"]);
44
+ }
45
+ });
46
+ it("returns error when fields variable is missing", async () => {
47
+ const result = await extract(makeJob({ input: "text" }), cfg);
48
+ expect(result.outcome).toBe("error");
49
+ if (result.outcome === "error")
50
+ expect(result.errorCode).toBe("AI_INVALID_INPUT");
51
+ });
52
+ it("returns error on parse failure", async () => {
53
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue("The fields are: name=Acme");
54
+ const result = await extract(makeJob({ input: "text", fields: ["name"] }), cfg);
55
+ expect(result.outcome).toBe("error");
56
+ if (result.outcome === "error")
57
+ expect(result.errorCode).toBe("AI_PARSE_ERROR");
58
+ });
59
+ it("returns fail on RetryableError", async () => {
60
+ vi.spyOn(llmModule, "callLlm").mockRejectedValue(new llmModule.RetryableError("rate limited"));
61
+ const result = await extract(makeJob({ input: "text", fields: ["name"] }), cfg);
62
+ expect(result.outcome).toBe("fail");
63
+ });
64
+ });
65
+ //# sourceMappingURL=extract.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.test.js","sourceRoot":"","sources":["../../src/operations/extract.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { WorkerJob, WorkerJobResult } from "@bpmnkit/cli-sdk";
2
+ import type { AiWorkerConfig } from "../config.js";
3
+ /**
4
+ * Summarizes job.variables.input.
5
+ *
6
+ * Required job variables:
7
+ * - input: string — the text to summarize
8
+ *
9
+ * Optional:
10
+ * - maxWords: number — target word count (default: 100)
11
+ * - style: "bullet" | "paragraph" — output style (default: "paragraph")
12
+ *
13
+ * Output variables: summary, wordCount, aiModel, processedAt
14
+ */
15
+ export declare function summarize(job: WorkerJob, config: AiWorkerConfig): Promise<WorkerJobResult>;
16
+ //# sourceMappingURL=summarize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../../src/operations/summarize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAclD;;;;;;;;;;;GAWG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAwChG"}
@@ -0,0 +1,59 @@
1
+ import { RetryableError, callLlm, parseJsonResponse } from "../llm.js";
2
+ const SYSTEM_PROMPT = `You are a text summarization assistant.
3
+ The user will provide text to summarize along with a target length and style.
4
+ Respond ONLY with valid JSON matching this exact shape:
5
+ { "summary": "<the summary text>", "wordCount": <integer> }
6
+ Do not include any prose outside the JSON object.`;
7
+ /**
8
+ * Summarizes job.variables.input.
9
+ *
10
+ * Required job variables:
11
+ * - input: string — the text to summarize
12
+ *
13
+ * Optional:
14
+ * - maxWords: number — target word count (default: 100)
15
+ * - style: "bullet" | "paragraph" — output style (default: "paragraph")
16
+ *
17
+ * Output variables: summary, wordCount, aiModel, processedAt
18
+ */
19
+ export async function summarize(job, config) {
20
+ const input = String(job.variables.input ?? "");
21
+ const maxWords = Number(job.variables.maxWords ?? 100);
22
+ const style = String(job.variables.style ?? "paragraph");
23
+ const userMessage = `Summarize the following text in ${style} style, targeting ${maxWords} words:\n\n${input}`;
24
+ let raw;
25
+ try {
26
+ raw = await callLlm(SYSTEM_PROMPT, userMessage, config);
27
+ }
28
+ catch (err) {
29
+ if (err instanceof RetryableError) {
30
+ return { outcome: "fail", errorMessage: err.message, retries: 2, retryBackOff: 30_000 };
31
+ }
32
+ return {
33
+ outcome: "error",
34
+ errorCode: "AI_API_ERROR",
35
+ errorMessage: err instanceof Error ? err.message : String(err),
36
+ };
37
+ }
38
+ let parsed;
39
+ try {
40
+ parsed = parseJsonResponse(raw);
41
+ }
42
+ catch {
43
+ return {
44
+ outcome: "error",
45
+ errorCode: "AI_PARSE_ERROR",
46
+ errorMessage: `Model returned non-JSON response: ${raw.slice(0, 200)}`,
47
+ };
48
+ }
49
+ return {
50
+ outcome: "complete",
51
+ variables: {
52
+ summary: parsed.summary,
53
+ wordCount: parsed.wordCount,
54
+ aiModel: config.model,
55
+ processedAt: new Date().toISOString(),
56
+ },
57
+ };
58
+ }
59
+ //# sourceMappingURL=summarize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../src/operations/summarize.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAOtE,MAAM,aAAa,GAAG;;;;kDAI4B,CAAA;AAElD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAc,EAAE,MAAsB;IACrE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;IAC/C,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAA;IACtD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,IAAI,WAAW,CAAC,CAAA;IACxD,MAAM,WAAW,GAAG,mCAAmC,KAAK,qBAAqB,QAAQ,cAAc,KAAK,EAAE,CAAA;IAE9G,IAAI,GAAW,CAAA;IACf,IAAI,CAAC;QACJ,GAAG,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,WAAW,EAAE,MAAM,CAAC,CAAA;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;YACnC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAA;QACxF,CAAC;QACD,OAAO;YACN,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,cAAc;YACzB,YAAY,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SAC9D,CAAA;IACF,CAAC;IAED,IAAI,MAAyB,CAAA;IAC7B,IAAI,CAAC;QACJ,MAAM,GAAG,iBAAiB,CAAoB,GAAG,CAAC,CAAA;IACnD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO;YACN,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,gBAAgB;YAC3B,YAAY,EAAE,qCAAqC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;SACtE,CAAA;IACF,CAAC;IAED,OAAO;QACN,OAAO,EAAE,UAAU;QACnB,SAAS,EAAE;YACV,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,OAAO,EAAE,MAAM,CAAC,KAAK;YACrB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACrC;KACD,CAAA;AACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=summarize.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize.test.d.ts","sourceRoot":"","sources":["../../src/operations/summarize.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import * as llmModule from "../llm.js";
3
+ import { summarize } from "./summarize.js";
4
+ const cfg = {
5
+ apiKey: "test",
6
+ model: "claude-test",
7
+ maxTokens: 512,
8
+ timeoutMs: 5000,
9
+ };
10
+ function makeJob(variables) {
11
+ return {
12
+ jobKey: "1",
13
+ processDefinitionId: "p1",
14
+ elementId: "e1",
15
+ processInstanceKey: "i1",
16
+ variables,
17
+ };
18
+ }
19
+ describe("summarize", () => {
20
+ it("returns complete with summary and wordCount", async () => {
21
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue(JSON.stringify({ summary: "A short summary.", wordCount: 3 }));
22
+ const result = await summarize(makeJob({ input: "Long text here..." }), cfg);
23
+ expect(result.outcome).toBe("complete");
24
+ if (result.outcome === "complete") {
25
+ expect(result.variables.summary).toBe("A short summary.");
26
+ expect(result.variables.wordCount).toBe(3);
27
+ }
28
+ });
29
+ it("returns error on parse failure", async () => {
30
+ vi.spyOn(llmModule, "callLlm").mockResolvedValue("Here is the summary: blah blah.");
31
+ const result = await summarize(makeJob({ input: "text" }), cfg);
32
+ expect(result.outcome).toBe("error");
33
+ if (result.outcome === "error")
34
+ expect(result.errorCode).toBe("AI_PARSE_ERROR");
35
+ });
36
+ it("returns fail on RetryableError", async () => {
37
+ vi.spyOn(llmModule, "callLlm").mockRejectedValue(new llmModule.RetryableError("timeout"));
38
+ const result = await summarize(makeJob({ input: "text" }), cfg);
39
+ expect(result.outcome).toBe("fail");
40
+ });
41
+ });
42
+ //# sourceMappingURL=summarize.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize.test.js","sourceRoot":"","sources":["../../src/operations/summarize.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAEjD,OAAO,KAAK,SAAS,MAAM,WAAW,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C,MAAM,GAAG,GAAmB;IAC3B,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,aAAa;IACpB,SAAS,EAAE,GAAG;IACd,SAAS,EAAE,IAAI;CACf,CAAA;AAED,SAAS,OAAO,CAAC,SAAkC;IAClD,OAAO;QACN,MAAM,EAAE,GAAG;QACX,mBAAmB,EAAE,IAAI;QACzB,SAAS,EAAE,IAAI;QACf,kBAAkB,EAAE,IAAI;QACxB,SAAS;KACT,CAAA;AACF,CAAC;AAED,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC5D,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,iBAAiB,CAC/C,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAC7D,CAAA;QACD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,EAAE,GAAG,CAAC,CAAA;QAC5E,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACvC,IAAI,MAAM,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;YACzD,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAC3C,CAAC;IACF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC/C,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,iBAAiB,CAAC,iCAAiC,CAAC,CAAA;QACnF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,CAAA;QAC/D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACpC,IAAI,MAAM,CAAC,OAAO,KAAK,OAAO;YAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;IAChF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC/C,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,iBAAiB,CAAC,IAAI,SAAS,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAA;QACzF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,CAAA;QAC/D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}