@loopstack/observability-examples 0.1.0 → 0.1.1
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 +60 -6
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/observability-examples.module.d.ts +6 -1
- package/dist/observability-examples.module.d.ts.map +1 -1
- package/dist/observability-examples.module.js +36 -4
- package/dist/observability-examples.module.js.map +1 -1
- package/dist/workflows/audit-log/audit-log-example.workflow.d.ts +10 -0
- package/dist/workflows/audit-log/audit-log-example.workflow.d.ts.map +1 -0
- package/dist/workflows/audit-log/audit-log-example.workflow.js +66 -0
- package/dist/workflows/audit-log/audit-log-example.workflow.js.map +1 -0
- package/dist/workflows/audit-log/listeners/audit.listener.d.ts +9 -0
- package/dist/workflows/audit-log/listeners/audit.listener.d.ts.map +1 -0
- package/dist/workflows/audit-log/listeners/audit.listener.js +44 -0
- package/dist/workflows/audit-log/listeners/audit.listener.js.map +1 -0
- package/dist/workflows/audit-log/services/audit-log.service.d.ts +11 -0
- package/dist/workflows/audit-log/services/audit-log.service.d.ts.map +1 -0
- package/dist/workflows/audit-log/services/audit-log.service.js +28 -0
- package/dist/workflows/audit-log/services/audit-log.service.js.map +1 -0
- package/dist/workflows/custom-calculator/calculators/words-processed.calculator.d.ts +7 -0
- package/dist/workflows/custom-calculator/calculators/words-processed.calculator.d.ts.map +1 -0
- package/dist/workflows/custom-calculator/calculators/words-processed.calculator.js +15 -0
- package/dist/workflows/custom-calculator/calculators/words-processed.calculator.js.map +1 -0
- package/dist/workflows/custom-calculator/custom-calculator-example.workflow.d.ts +17 -0
- package/dist/workflows/custom-calculator/custom-calculator-example.workflow.d.ts.map +1 -0
- package/dist/workflows/custom-calculator/custom-calculator-example.workflow.js +66 -0
- package/dist/workflows/custom-calculator/custom-calculator-example.workflow.js.map +1 -0
- package/dist/workflows/custom-calculator/tools/analyze-text.tool.d.ts +19 -0
- package/dist/workflows/custom-calculator/tools/analyze-text.tool.d.ts.map +1 -0
- package/dist/workflows/custom-calculator/tools/analyze-text.tool.js +35 -0
- package/dist/workflows/custom-calculator/tools/analyze-text.tool.js.map +1 -0
- package/dist/workflows/quota/quota-example.workflow.js +1 -1
- package/dist/workflows/quota/quota-example.workflow.js.map +1 -1
- package/dist/workflows/tracing/interceptors/tracing.interceptor.d.ts +10 -0
- package/dist/workflows/tracing/interceptors/tracing.interceptor.d.ts.map +1 -0
- package/dist/workflows/tracing/interceptors/tracing.interceptor.js +48 -0
- package/dist/workflows/tracing/interceptors/tracing.interceptor.js.map +1 -0
- package/dist/workflows/tracing/services/tool-trace.service.d.ts +12 -0
- package/dist/workflows/tracing/services/tool-trace.service.d.ts.map +1 -0
- package/dist/workflows/tracing/services/tool-trace.service.js +28 -0
- package/dist/workflows/tracing/services/tool-trace.service.js.map +1 -0
- package/dist/workflows/tracing/tools/simulate-work.tool.d.ts +20 -0
- package/dist/workflows/tracing/tools/simulate-work.tool.d.ts.map +1 -0
- package/dist/workflows/tracing/tools/simulate-work.tool.js +36 -0
- package/dist/workflows/tracing/tools/simulate-work.tool.js.map +1 -0
- package/dist/workflows/tracing/tracing-example.workflow.d.ts +17 -0
- package/dist/workflows/tracing/tracing-example.workflow.d.ts.map +1 -0
- package/dist/workflows/tracing/tracing-example.workflow.js +62 -0
- package/dist/workflows/tracing/tracing-example.workflow.js.map +1 -0
- package/package.json +3 -1
- package/src/index.ts +10 -0
- package/src/observability-examples.module.ts +33 -6
- package/src/workflows/audit-log/audit-log-example.workflow.ts +43 -0
- package/src/workflows/audit-log/listeners/audit.listener.ts +22 -0
- package/src/workflows/audit-log/services/audit-log.service.ts +25 -0
- package/src/workflows/custom-calculator/__tests__/custom-calculator-example.spec.ts +27 -0
- package/src/workflows/custom-calculator/calculators/words-processed.calculator.ts +15 -0
- package/src/workflows/custom-calculator/custom-calculator-example.workflow.ts +52 -0
- package/src/workflows/custom-calculator/tools/analyze-text.tool.ts +32 -0
- package/src/workflows/quota/quota-example.workflow.ts +1 -1
- package/src/workflows/tracing/interceptors/tracing.interceptor.ts +31 -0
- package/src/workflows/tracing/services/tool-trace.service.ts +26 -0
- package/src/workflows/tracing/tools/simulate-work.tool.ts +33 -0
- package/src/workflows/tracing/tracing-example.workflow.ts +47 -0
|
@@ -14,7 +14,7 @@ const zod_1 = require("zod");
|
|
|
14
14
|
const common_1 = require("@loopstack/common");
|
|
15
15
|
const quota_1 = require("@loopstack/quota");
|
|
16
16
|
const QuotaExampleArgsSchema = zod_1.z.object({
|
|
17
|
-
quotaType: zod_1.z.string().default('
|
|
17
|
+
quotaType: zod_1.z.string().default('llm-cost'),
|
|
18
18
|
reportAmount: zod_1.z.number().default(1000),
|
|
19
19
|
});
|
|
20
20
|
let QuotaExampleWorkflow = class QuotaExampleWorkflow extends common_1.BaseWorkflow {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quota-example.workflow.js","sourceRoot":"","sources":["../../../src/workflows/quota/quota-example.workflow.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6BAAwB;AACxB,8CAAyF;AAEzF,4CAAsD;AAQtD,MAAM,sBAAsB,GAAG,OAAC,CAAC,MAAM,CAAC;IACtC,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"quota-example.workflow.js","sourceRoot":"","sources":["../../../src/workflows/quota/quota-example.workflow.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6BAAwB;AACxB,8CAAyF;AAEzF,4CAAsD;AAQtD,MAAM,sBAAsB,GAAG,OAAC,CAAC,MAAM,CAAC;IACtC,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC;IACzC,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;CACvC,CAAC,CAAC;AAUI,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,qBAA8B;IACzC;IAA7B,YAA6B,WAA+B;QAC1D,KAAK,EAAE,CAAC;QADmB,gBAAW,GAAX,WAAW,CAAoB;IAE5D,CAAC;IAGK,AAAN,KAAK,CAAC,UAAU,CAAC,MAAyB,EAAE,GAAiC;QAC3E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,IAAI,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEhG,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;QACjC,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,yBAAgB,EAAE;YAC9C,QAAQ,EAAE;gBACR,gBAAgB;gBAChB,EAAE;gBACF,gBAAgB,OAAO,IAAI;gBAC3B,aAAa,MAAM,CAAC,IAAI,IAAI;gBAC5B,cAAc,MAAM,CAAC,KAAK,IAAI;aAC/B,CAAC,IAAI,CAAC,IAAI,CAAC;SACb,CAAC,CAAC;QAEH,IAAI,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IAClF,CAAC;IAGK,AAAN,KAAK,CAAC,WAAW,CAAC,MAAyB,EAAE,GAAiC;QAC5E,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,yBAAgB,EAAE;gBAC9C,QAAQ,EAAE,0DAA0D;aACrE,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAEpG,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,yBAAgB,EAAE;YAC9C,QAAQ,EAAE;gBACR,aAAa;gBACb,EAAE;gBACF,aAAa,GAAG,CAAC,IAAI,CAAC,YAAY,iBAAiB,GAAG,CAAC,IAAI,CAAC,SAAS,yBAAyB;aAC/F,CAAC,IAAI,CAAC,IAAI,CAAC;SACb,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AA1CY,oDAAoB;AAMzB;IADL,IAAA,mBAAU,EAAC,EAAE,EAAE,EAAE,eAAe,EAAE,CAAC;;;;sDAgBnC;AAGK;IADL,IAAA,mBAAU,EAAC,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;;;;uDAkBhD;+BAzCU,oBAAoB;IANhC,IAAA,iBAAQ,EAAC;QACR,KAAK,EAAE,+BAA+B;QACtC,WAAW,EACT,0LAA0L;QAC5L,MAAM,EAAE,sBAAsB;KAC/B,CAAC;qCAE0C,0BAAkB;GADjD,oBAAoB,CA0ChC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ToolEnvelope, ToolExecutionContext, ToolInterceptor } from '@loopstack/common';
|
|
2
|
+
import { ToolTraceService } from '../services/tool-trace.service';
|
|
3
|
+
export declare class TracingInterceptor implements ToolInterceptor {
|
|
4
|
+
private readonly traceStore;
|
|
5
|
+
private readonly logger;
|
|
6
|
+
constructor(traceStore: ToolTraceService);
|
|
7
|
+
intercept(context: ToolExecutionContext, next: () => Promise<ToolEnvelope>): Promise<ToolEnvelope>;
|
|
8
|
+
private trace;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=tracing.interceptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing.interceptor.d.ts","sourceRoot":"","sources":["../../../../src/workflows/tracing/interceptors/tracing.interceptor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,eAAe,EAAsB,MAAM,mBAAmB,CAAC;AAC5G,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAElE,qBACa,kBAAmB,YAAW,eAAe;IAG5C,OAAO,CAAC,QAAQ,CAAC,UAAU;IAFvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;gBAEjC,UAAU,EAAE,gBAAgB;IAEnD,SAAS,CAAC,OAAO,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;IAexG,OAAO,CAAC,KAAK;CAKd"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var TracingInterceptor_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.TracingInterceptor = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const common_2 = require("@loopstack/common");
|
|
16
|
+
const tool_trace_service_1 = require("../services/tool-trace.service");
|
|
17
|
+
let TracingInterceptor = TracingInterceptor_1 = class TracingInterceptor {
|
|
18
|
+
traceStore;
|
|
19
|
+
logger = new common_1.Logger(TracingInterceptor_1.name);
|
|
20
|
+
constructor(traceStore) {
|
|
21
|
+
this.traceStore = traceStore;
|
|
22
|
+
}
|
|
23
|
+
async intercept(context, next) {
|
|
24
|
+
const toolName = context.tool.constructor.name;
|
|
25
|
+
const workflowId = context.runContext.workflowId;
|
|
26
|
+
const startTime = performance.now();
|
|
27
|
+
try {
|
|
28
|
+
const result = await next();
|
|
29
|
+
this.trace(toolName, workflowId, startTime, true);
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
this.trace(toolName, workflowId, startTime, false);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
trace(toolName, workflowId, startTime, success) {
|
|
38
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
39
|
+
this.traceStore.record({ toolName, workflowId, durationMs, success });
|
|
40
|
+
this.logger.log(`${toolName} — ${success ? 'ok' : 'failed'} in ${durationMs}ms (workflow ${workflowId})`);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
exports.TracingInterceptor = TracingInterceptor;
|
|
44
|
+
exports.TracingInterceptor = TracingInterceptor = TracingInterceptor_1 = __decorate([
|
|
45
|
+
(0, common_2.UseToolInterceptor)({ priority: 10 }),
|
|
46
|
+
__metadata("design:paramtypes", [tool_trace_service_1.ToolTraceService])
|
|
47
|
+
], TracingInterceptor);
|
|
48
|
+
//# sourceMappingURL=tracing.interceptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing.interceptor.js","sourceRoot":"","sources":["../../../../src/workflows/tracing/interceptors/tracing.interceptor.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAwC;AACxC,8CAA4G;AAC5G,uEAAkE;AAG3D,IAAM,kBAAkB,0BAAxB,MAAM,kBAAkB;IAGA;IAFZ,MAAM,GAAG,IAAI,eAAM,CAAC,oBAAkB,CAAC,IAAI,CAAC,CAAC;IAE9D,YAA6B,UAA4B;QAA5B,eAAU,GAAV,UAAU,CAAkB;IAAG,CAAC;IAE7D,KAAK,CAAC,SAAS,CAAC,OAA6B,EAAE,IAAiC;QAC9E,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QAC/C,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC;QACjD,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAEpC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;YAClD,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;YACnD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,QAAgB,EAAE,UAAkB,EAAE,SAAiB,EAAE,OAAgB;QACrF,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;QAC7D,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,MAAM,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,OAAO,UAAU,gBAAgB,UAAU,GAAG,CAAC,CAAC;IAC5G,CAAC;CACF,CAAA;AAzBY,gDAAkB;6BAAlB,kBAAkB;IAD9B,IAAA,2BAAkB,EAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;qCAIM,qCAAgB;GAH9C,kBAAkB,CAyB9B"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ToolTraceEntry {
|
|
2
|
+
toolName: string;
|
|
3
|
+
workflowId: string;
|
|
4
|
+
durationMs: number;
|
|
5
|
+
success: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare class ToolTraceService {
|
|
8
|
+
private readonly entries;
|
|
9
|
+
record(entry: ToolTraceEntry): void;
|
|
10
|
+
forWorkflow(workflowId: string): ToolTraceEntry[];
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=tool-trace.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-trace.service.d.ts","sourceRoot":"","sources":["../../../../src/workflows/tracing/services/tool-trace.service.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAID,qBACa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAEhD,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAOnC,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,EAAE;CAGlD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ToolTraceService = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const MAX_ENTRIES = 100;
|
|
12
|
+
let ToolTraceService = class ToolTraceService {
|
|
13
|
+
entries = [];
|
|
14
|
+
record(entry) {
|
|
15
|
+
this.entries.push(entry);
|
|
16
|
+
if (this.entries.length > MAX_ENTRIES) {
|
|
17
|
+
this.entries.shift();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
forWorkflow(workflowId) {
|
|
21
|
+
return this.entries.filter((entry) => entry.workflowId === workflowId);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
exports.ToolTraceService = ToolTraceService;
|
|
25
|
+
exports.ToolTraceService = ToolTraceService = __decorate([
|
|
26
|
+
(0, common_1.Injectable)()
|
|
27
|
+
], ToolTraceService);
|
|
28
|
+
//# sourceMappingURL=tool-trace.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-trace.service.js","sourceRoot":"","sources":["../../../../src/workflows/tracing/services/tool-trace.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAS5C,MAAM,WAAW,GAAG,GAAG,CAAC;AAGjB,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IACV,OAAO,GAAqB,EAAE,CAAC;IAEhD,MAAM,CAAC,KAAqB;QAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;YACtC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;IAED,WAAW,CAAC,UAAkB;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC;IACzE,CAAC;CACF,CAAA;AAbY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;GACA,gBAAgB,CAa5B"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BaseTool, ToolEnvelope } from '@loopstack/common';
|
|
3
|
+
declare const SimulateWorkSchema: z.ZodObject<{
|
|
4
|
+
label: z.ZodString;
|
|
5
|
+
delayMs: z.ZodNumber;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
type SimulateWorkArgs = z.infer<typeof SimulateWorkSchema>;
|
|
8
|
+
export interface SimulateWorkToolResult {
|
|
9
|
+
label: string;
|
|
10
|
+
delayMs: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const SimulateWorkToolResultSchema: z.ZodObject<{
|
|
13
|
+
label: z.ZodString;
|
|
14
|
+
delayMs: z.ZodNumber;
|
|
15
|
+
}, z.core.$strict>;
|
|
16
|
+
export declare class SimulateWorkTool extends BaseTool<SimulateWorkArgs, object, SimulateWorkToolResult> {
|
|
17
|
+
protected handle(args: SimulateWorkArgs): Promise<ToolEnvelope<SimulateWorkToolResult>>;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=simulate-work.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulate-work.tool.d.ts","sourceRoot":"","sources":["../../../../src/workflows/tracing/tools/simulate-work.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAQ,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjE,QAAA,MAAM,kBAAkB;;;iBAGtB,CAAC;AAEH,KAAK,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE3D,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,4BAA4B;;;kBAGvC,CAAC;AAEH,qBAOa,gBAAiB,SAAQ,QAAQ,CAAC,gBAAgB,EAAE,MAAM,EAAE,sBAAsB,CAAC;cAC9E,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC;CAI9F"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.SimulateWorkTool = exports.SimulateWorkToolResultSchema = void 0;
|
|
10
|
+
const zod_1 = require("zod");
|
|
11
|
+
const common_1 = require("@loopstack/common");
|
|
12
|
+
const SimulateWorkSchema = zod_1.z.object({
|
|
13
|
+
label: zod_1.z.string(),
|
|
14
|
+
delayMs: zod_1.z.number().min(0).max(5000),
|
|
15
|
+
});
|
|
16
|
+
exports.SimulateWorkToolResultSchema = zod_1.z.strictObject({
|
|
17
|
+
label: zod_1.z.string(),
|
|
18
|
+
delayMs: zod_1.z.number(),
|
|
19
|
+
});
|
|
20
|
+
let SimulateWorkTool = class SimulateWorkTool extends common_1.BaseTool {
|
|
21
|
+
async handle(args) {
|
|
22
|
+
await new Promise((resolve) => setTimeout(resolve, args.delayMs));
|
|
23
|
+
return { data: { label: args.label, delayMs: args.delayMs } };
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
exports.SimulateWorkTool = SimulateWorkTool;
|
|
27
|
+
exports.SimulateWorkTool = SimulateWorkTool = __decorate([
|
|
28
|
+
(0, common_1.Tool)({
|
|
29
|
+
name: 'simulate_work',
|
|
30
|
+
description: 'Sleeps for the given duration so interceptors have something to measure.',
|
|
31
|
+
schema: SimulateWorkSchema,
|
|
32
|
+
resultSchema: exports.SimulateWorkToolResultSchema,
|
|
33
|
+
effects: 'none',
|
|
34
|
+
})
|
|
35
|
+
], SimulateWorkTool);
|
|
36
|
+
//# sourceMappingURL=simulate-work.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulate-work.tool.js","sourceRoot":"","sources":["../../../../src/workflows/tracing/tools/simulate-work.tool.ts"],"names":[],"mappings":";;;;;;;;;AAAA,6BAAwB;AACxB,8CAAiE;AAEjE,MAAM,kBAAkB,GAAG,OAAC,CAAC,MAAM,CAAC;IAClC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;IACjB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;CACrC,CAAC,CAAC;AASU,QAAA,4BAA4B,GAAG,OAAC,CAAC,YAAY,CAAC;IACzD,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;IACjB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;CACpB,CAAC,CAAC;AASI,IAAM,gBAAgB,GAAtB,MAAM,gBAAiB,SAAQ,iBAA0D;IACpF,KAAK,CAAC,MAAM,CAAC,IAAsB;QAC3C,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAClE,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;IAChE,CAAC;CACF,CAAA;AALY,4CAAgB;2BAAhB,gBAAgB;IAP5B,IAAA,aAAI,EAAC;QACJ,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,0EAA0E;QACvF,MAAM,EAAE,kBAAkB;QAC1B,YAAY,EAAE,oCAA4B;QAC1C,OAAO,EAAE,MAAM;KAChB,CAAC;GACW,gBAAgB,CAK5B"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BaseWorkflow } from '@loopstack/common';
|
|
3
|
+
import type { RunContext } from '@loopstack/common';
|
|
4
|
+
import { ToolTraceService } from './services/tool-trace.service';
|
|
5
|
+
import { SimulateWorkTool } from './tools/simulate-work.tool';
|
|
6
|
+
declare const TracingExampleArgsSchema: z.ZodObject<{
|
|
7
|
+
steps: z.ZodDefault<z.ZodNumber>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
type TracingExampleArgs = z.infer<typeof TracingExampleArgsSchema>;
|
|
10
|
+
export declare class TracingExampleWorkflow extends BaseWorkflow<TracingExampleArgs> {
|
|
11
|
+
private readonly simulateWorkTool;
|
|
12
|
+
private readonly traceStore;
|
|
13
|
+
constructor(simulateWorkTool: SimulateWorkTool, traceStore: ToolTraceService);
|
|
14
|
+
runAndTrace(_state: object, ctx: RunContext<TracingExampleArgs>): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=tracing-example.workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing-example.workflow.d.ts","sourceRoot":"","sources":["../../../src/workflows/tracing/tracing-example.workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAA0C,MAAM,mBAAmB,CAAC;AACzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAE9D,QAAA,MAAM,wBAAwB;;iBAE5B,CAAC;AAEH,KAAK,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEnE,qBAMa,sBAAuB,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IAExE,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU;gBADV,gBAAgB,EAAE,gBAAgB,EAClC,UAAU,EAAE,gBAAgB;IAMzC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,kBAAkB,CAAC;CAmBtE"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.TracingExampleWorkflow = void 0;
|
|
13
|
+
const zod_1 = require("zod");
|
|
14
|
+
const common_1 = require("@loopstack/common");
|
|
15
|
+
const tool_trace_service_1 = require("./services/tool-trace.service");
|
|
16
|
+
const simulate_work_tool_1 = require("./tools/simulate-work.tool");
|
|
17
|
+
const TracingExampleArgsSchema = zod_1.z.object({
|
|
18
|
+
steps: zod_1.z.number().min(1).max(10).default(3),
|
|
19
|
+
});
|
|
20
|
+
let TracingExampleWorkflow = class TracingExampleWorkflow extends common_1.BaseWorkflow {
|
|
21
|
+
simulateWorkTool;
|
|
22
|
+
traceStore;
|
|
23
|
+
constructor(simulateWorkTool, traceStore) {
|
|
24
|
+
super();
|
|
25
|
+
this.simulateWorkTool = simulateWorkTool;
|
|
26
|
+
this.traceStore = traceStore;
|
|
27
|
+
}
|
|
28
|
+
async runAndTrace(_state, ctx) {
|
|
29
|
+
for (let i = 1; i <= ctx.args.steps; i++) {
|
|
30
|
+
await this.simulateWorkTool.call({ label: `step-${i}`, delayMs: i * 100 });
|
|
31
|
+
}
|
|
32
|
+
const entries = this.traceStore.forWorkflow(ctx.workflowId);
|
|
33
|
+
await this.documentStore.save(common_1.MarkdownDocument, {
|
|
34
|
+
markdown: [
|
|
35
|
+
'## Tool Trace',
|
|
36
|
+
'',
|
|
37
|
+
'Collected by `TracingInterceptor` — it wraps every tool call in this app.',
|
|
38
|
+
'',
|
|
39
|
+
'| Tool | Duration | Status |',
|
|
40
|
+
'| ---- | -------- | ------ |',
|
|
41
|
+
...entries.map((e) => `| \`${e.toolName}\` | ${e.durationMs}ms | ${e.success ? '✅ ok' : '❌ failed'} |`),
|
|
42
|
+
].join('\n'),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
exports.TracingExampleWorkflow = TracingExampleWorkflow;
|
|
47
|
+
__decorate([
|
|
48
|
+
(0, common_1.Transition)({ to: 'end' }),
|
|
49
|
+
__metadata("design:type", Function),
|
|
50
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
51
|
+
__metadata("design:returntype", Promise)
|
|
52
|
+
], TracingExampleWorkflow.prototype, "runAndTrace", null);
|
|
53
|
+
exports.TracingExampleWorkflow = TracingExampleWorkflow = __decorate([
|
|
54
|
+
(0, common_1.Workflow)({
|
|
55
|
+
title: 'Observability - Tracing Interceptor Example',
|
|
56
|
+
description: 'Demonstrates a custom ToolInterceptor — @UseToolInterceptor() registers it app-wide, it measures every tool execution and stores trace entries in an injectable service. The workflow runs a few tool calls and renders the collected trace.',
|
|
57
|
+
schema: TracingExampleArgsSchema,
|
|
58
|
+
}),
|
|
59
|
+
__metadata("design:paramtypes", [simulate_work_tool_1.SimulateWorkTool,
|
|
60
|
+
tool_trace_service_1.ToolTraceService])
|
|
61
|
+
], TracingExampleWorkflow);
|
|
62
|
+
//# sourceMappingURL=tracing-example.workflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing-example.workflow.js","sourceRoot":"","sources":["../../../src/workflows/tracing/tracing-example.workflow.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6BAAwB;AACxB,8CAAyF;AAEzF,sEAAiE;AACjE,mEAA8D;AAE9D,MAAM,wBAAwB,GAAG,OAAC,CAAC,MAAM,CAAC;IACxC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;CAC5C,CAAC,CAAC;AAUI,IAAM,sBAAsB,GAA5B,MAAM,sBAAuB,SAAQ,qBAAgC;IAEvD;IACA;IAFnB,YACmB,gBAAkC,EAClC,UAA4B;QAE7C,KAAK,EAAE,CAAC;QAHS,qBAAgB,GAAhB,gBAAgB,CAAkB;QAClC,eAAU,GAAV,UAAU,CAAkB;IAG/C,CAAC;IAGK,AAAN,KAAK,CAAC,WAAW,CAAC,MAAc,EAAE,GAAmC;QACnE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAE5D,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,yBAAgB,EAAE;YAC9C,QAAQ,EAAE;gBACR,eAAe;gBACf,EAAE;gBACF,2EAA2E;gBAC3E,EAAE;gBACF,8BAA8B;gBAC9B,8BAA8B;gBAC9B,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,QAAQ,QAAQ,CAAC,CAAC,UAAU,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC;aACxG,CAAC,IAAI,CAAC,IAAI,CAAC;SACb,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AA5BY,wDAAsB;AAS3B;IADL,IAAA,mBAAU,EAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;;;;yDAmBzB;iCA3BU,sBAAsB;IANlC,IAAA,iBAAQ,EAAC;QACR,KAAK,EAAE,6CAA6C;QACpD,WAAW,EACT,8OAA8O;QAChP,MAAM,EAAE,wBAAwB;KACjC,CAAC;qCAGqC,qCAAgB;QACtB,qCAAgB;GAHpC,sBAAsB,CA4BlC"}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"quota",
|
|
10
10
|
"workflow"
|
|
11
11
|
],
|
|
12
|
-
"version": "0.1.
|
|
12
|
+
"version": "0.1.1",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Jakob Klippel",
|
|
@@ -31,8 +31,10 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@loopstack/common": "*",
|
|
34
|
+
"@loopstack/contracts": "*",
|
|
34
35
|
"@loopstack/quota": "*",
|
|
35
36
|
"@nestjs/common": "^11.1.19",
|
|
37
|
+
"@nestjs/event-emitter": "^3.0.1",
|
|
36
38
|
"zod": "^4.3.6"
|
|
37
39
|
},
|
|
38
40
|
"files": [
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
1
|
export * from './observability-examples.module';
|
|
2
2
|
export * from './workflows/quota/quota-example.workflow';
|
|
3
|
+
export * from './workflows/tracing/tracing-example.workflow';
|
|
4
|
+
export * from './workflows/tracing/services/tool-trace.service';
|
|
5
|
+
export * from './workflows/tracing/interceptors/tracing.interceptor';
|
|
6
|
+
export * from './workflows/tracing/tools/simulate-work.tool';
|
|
7
|
+
export * from './workflows/custom-calculator/custom-calculator-example.workflow';
|
|
8
|
+
export * from './workflows/custom-calculator/calculators/words-processed.calculator';
|
|
9
|
+
export * from './workflows/custom-calculator/tools/analyze-text.tool';
|
|
10
|
+
export * from './workflows/audit-log/audit-log-example.workflow';
|
|
11
|
+
export * from './workflows/audit-log/services/audit-log.service';
|
|
12
|
+
export * from './workflows/audit-log/listeners/audit.listener';
|
|
@@ -1,15 +1,42 @@
|
|
|
1
|
-
import { Module } from '@nestjs/common';
|
|
1
|
+
import { Module, OnModuleInit } from '@nestjs/common';
|
|
2
2
|
import { StudioApp } from '@loopstack/common';
|
|
3
|
-
import { QuotaModule } from '@loopstack/quota';
|
|
3
|
+
import { QuotaCalculatorRegistry, QuotaModule } from '@loopstack/quota';
|
|
4
|
+
import { AuditLogExampleWorkflow } from './workflows/audit-log/audit-log-example.workflow';
|
|
5
|
+
import { AuditListener } from './workflows/audit-log/listeners/audit.listener';
|
|
6
|
+
import { AuditLogService } from './workflows/audit-log/services/audit-log.service';
|
|
7
|
+
import { WordsProcessedQuotaCalculator } from './workflows/custom-calculator/calculators/words-processed.calculator';
|
|
8
|
+
import { CustomCalculatorExampleWorkflow } from './workflows/custom-calculator/custom-calculator-example.workflow';
|
|
9
|
+
import { AnalyzeTextTool } from './workflows/custom-calculator/tools/analyze-text.tool';
|
|
4
10
|
import { QuotaExampleWorkflow } from './workflows/quota/quota-example.workflow';
|
|
11
|
+
import { TracingInterceptor } from './workflows/tracing/interceptors/tracing.interceptor';
|
|
12
|
+
import { ToolTraceService } from './workflows/tracing/services/tool-trace.service';
|
|
13
|
+
import { SimulateWorkTool } from './workflows/tracing/tools/simulate-work.tool';
|
|
14
|
+
import { TracingExampleWorkflow } from './workflows/tracing/tracing-example.workflow';
|
|
5
15
|
|
|
6
16
|
@StudioApp({
|
|
7
17
|
title: 'Observability Examples',
|
|
8
|
-
workflows: [QuotaExampleWorkflow],
|
|
18
|
+
workflows: [QuotaExampleWorkflow, TracingExampleWorkflow, CustomCalculatorExampleWorkflow, AuditLogExampleWorkflow],
|
|
9
19
|
})
|
|
10
20
|
@Module({
|
|
11
21
|
imports: [QuotaModule.forRoot({ enabled: false })],
|
|
12
|
-
providers: [
|
|
13
|
-
|
|
22
|
+
providers: [
|
|
23
|
+
QuotaExampleWorkflow,
|
|
24
|
+
TracingExampleWorkflow,
|
|
25
|
+
ToolTraceService,
|
|
26
|
+
TracingInterceptor,
|
|
27
|
+
SimulateWorkTool,
|
|
28
|
+
CustomCalculatorExampleWorkflow,
|
|
29
|
+
AnalyzeTextTool,
|
|
30
|
+
AuditLogExampleWorkflow,
|
|
31
|
+
AuditLogService,
|
|
32
|
+
AuditListener,
|
|
33
|
+
],
|
|
34
|
+
exports: [QuotaExampleWorkflow, TracingExampleWorkflow, CustomCalculatorExampleWorkflow, AuditLogExampleWorkflow],
|
|
14
35
|
})
|
|
15
|
-
export class ObservabilityExamplesModule {
|
|
36
|
+
export class ObservabilityExamplesModule implements OnModuleInit {
|
|
37
|
+
constructor(private readonly calculatorRegistry: QuotaCalculatorRegistry) {}
|
|
38
|
+
|
|
39
|
+
onModuleInit() {
|
|
40
|
+
this.calculatorRegistry.register('AnalyzeTextTool', new WordsProcessedQuotaCalculator());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { BaseWorkflow, MarkdownDocument, MessageDocument, Transition, Workflow } from '@loopstack/common';
|
|
2
|
+
import type { RunContext } from '@loopstack/common';
|
|
3
|
+
import { AuditLogService } from './services/audit-log.service';
|
|
4
|
+
|
|
5
|
+
@Workflow({
|
|
6
|
+
title: 'Observability - Audit Log Example',
|
|
7
|
+
description:
|
|
8
|
+
'Demonstrates subscribing to framework events — an @OnEvent("client.message") listener records every workflow and document lifecycle event into an injectable service. The workflow emits a few events, then renders its own audit trail.',
|
|
9
|
+
})
|
|
10
|
+
export class AuditLogExampleWorkflow extends BaseWorkflow {
|
|
11
|
+
constructor(private readonly auditLog: AuditLogService) {
|
|
12
|
+
super();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Transition({ to: 'auditing' })
|
|
16
|
+
async doWork() {
|
|
17
|
+
await this.documentStore.save(MessageDocument, {
|
|
18
|
+
role: 'assistant',
|
|
19
|
+
text: 'Doing some work — each saved document emits a `document.created` event.',
|
|
20
|
+
});
|
|
21
|
+
await this.documentStore.save(MessageDocument, {
|
|
22
|
+
role: 'assistant',
|
|
23
|
+
text: 'The audit listener captures these events app-wide.',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Transition({ from: 'auditing', to: 'end' })
|
|
28
|
+
async renderAudit(_state: object, ctx: RunContext) {
|
|
29
|
+
const entries = this.auditLog.forWorkflow(ctx.workflowId);
|
|
30
|
+
|
|
31
|
+
await this.documentStore.save(MarkdownDocument, {
|
|
32
|
+
markdown: [
|
|
33
|
+
'## Audit Trail',
|
|
34
|
+
'',
|
|
35
|
+
`Captured by \`AuditListener\` via \`@OnEvent('client.message')\` for workflow \`${ctx.workflowId}\`.`,
|
|
36
|
+
'',
|
|
37
|
+
'| Event type | User |',
|
|
38
|
+
'| ---------- | ---- |',
|
|
39
|
+
...entries.map((e) => `| \`${e.type}\` | \`${e.userId ?? 'anonymous'}\` |`),
|
|
40
|
+
].join('\n'),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
|
+
import { OnEvent } from '@nestjs/event-emitter';
|
|
3
|
+
import type { ClientMessage } from '@loopstack/contracts/events';
|
|
4
|
+
import { AuditLogService } from '../services/audit-log.service';
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class AuditListener {
|
|
8
|
+
private readonly logger = new Logger(AuditListener.name);
|
|
9
|
+
|
|
10
|
+
constructor(private readonly auditLog: AuditLogService) {}
|
|
11
|
+
|
|
12
|
+
@OnEvent('client.message')
|
|
13
|
+
handle(payload: ClientMessage): void {
|
|
14
|
+
const workflowId = 'workflowId' in payload ? payload.workflowId : undefined;
|
|
15
|
+
this.auditLog.record({
|
|
16
|
+
type: payload.type,
|
|
17
|
+
workflowId,
|
|
18
|
+
userId: payload.userId,
|
|
19
|
+
});
|
|
20
|
+
this.logger.log(`${payload.type} (workflow ${workflowId ?? '-'})`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
export interface AuditEntry {
|
|
4
|
+
type: string;
|
|
5
|
+
workflowId?: string;
|
|
6
|
+
userId: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const MAX_ENTRIES = 200;
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class AuditLogService {
|
|
13
|
+
private readonly entries: AuditEntry[] = [];
|
|
14
|
+
|
|
15
|
+
record(entry: AuditEntry): void {
|
|
16
|
+
this.entries.push(entry);
|
|
17
|
+
if (this.entries.length > MAX_ENTRIES) {
|
|
18
|
+
this.entries.shift();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
forWorkflow(workflowId: string): AuditEntry[] {
|
|
23
|
+
return this.entries.filter((entry) => entry.workflowId === workflowId);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { runWorkflow } from '@loopstack/testing';
|
|
3
|
+
import { ObservabilityExamplesModule } from '../../../observability-examples.module';
|
|
4
|
+
import { CustomCalculatorExampleWorkflow } from '../custom-calculator-example.workflow';
|
|
5
|
+
import { AnalyzeTextTool } from '../tools/analyze-text.tool';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The counter-lesson to replay: here the tool runs *live*. The whole point is the interceptor
|
|
9
|
+
* and quota calculator that fire around a real tool call, so mocking the tool would defeat the
|
|
10
|
+
* test. Importing the app module wires up the `QuotaInterceptor` and registers the custom
|
|
11
|
+
* calculator; `QuotaModule.forRoot({ enabled: false })` makes quota a no-op, so no Redis.
|
|
12
|
+
*/
|
|
13
|
+
describe('CustomCalculatorExampleWorkflow', () => {
|
|
14
|
+
it('runs the analyze tool live and reports the analysis with quota disabled', async () => {
|
|
15
|
+
const run = await runWorkflow(
|
|
16
|
+
CustomCalculatorExampleWorkflow,
|
|
17
|
+
{ text: 'The quick brown fox jumps over the lazy dog' },
|
|
18
|
+
{ imports: [ObservabilityExamplesModule], providers: [AnalyzeTextTool] },
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
expect(run.error).toBeUndefined();
|
|
22
|
+
expect(run.status).toBe('completed');
|
|
23
|
+
const markdown = run.documents.map((d) => (d.content as { markdown?: string }).markdown ?? '').join('\n');
|
|
24
|
+
expect(markdown).toContain('- Words: `9`');
|
|
25
|
+
expect(markdown).toContain('- Limit: `-1`');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ToolEnvelope, ToolExecutionContext } from '@loopstack/common';
|
|
2
|
+
import { QuotaUsage, ToolQuotaCalculator } from '@loopstack/quota';
|
|
3
|
+
import type { AnalyzeTextToolResult } from '../tools/analyze-text.tool';
|
|
4
|
+
|
|
5
|
+
export class WordsProcessedQuotaCalculator implements ToolQuotaCalculator {
|
|
6
|
+
quotaType = 'words-processed';
|
|
7
|
+
|
|
8
|
+
calculateQuotaUsage(_context: ToolExecutionContext, result: ToolEnvelope): QuotaUsage | null {
|
|
9
|
+
const data = result.data as AnalyzeTextToolResult | undefined;
|
|
10
|
+
if (typeof data?.words !== 'number') {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return { quotaType: this.quotaType, actualAmount: data.words };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BaseWorkflow, MarkdownDocument, Transition, Workflow } from '@loopstack/common';
|
|
3
|
+
import type { RunContext } from '@loopstack/common';
|
|
4
|
+
import { QuotaClientService } from '@loopstack/quota';
|
|
5
|
+
import { AnalyzeTextTool } from './tools/analyze-text.tool';
|
|
6
|
+
import type { AnalyzeTextToolResult } from './tools/analyze-text.tool';
|
|
7
|
+
|
|
8
|
+
const CustomCalculatorExampleArgsSchema = z.object({
|
|
9
|
+
text: z.string().default('The quick brown fox jumps over the lazy dog'),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
type CustomCalculatorExampleArgs = z.infer<typeof CustomCalculatorExampleArgsSchema>;
|
|
13
|
+
|
|
14
|
+
@Workflow({
|
|
15
|
+
title: 'Observability - Custom Quota Calculator Example',
|
|
16
|
+
description:
|
|
17
|
+
'Demonstrates a custom ToolQuotaCalculator — WordsProcessedQuotaCalculator meters AnalyzeTextTool by words in the result. The built-in QuotaInterceptor checks and reports the quota automatically; the workflow only calls the tool.',
|
|
18
|
+
schema: CustomCalculatorExampleArgsSchema,
|
|
19
|
+
})
|
|
20
|
+
export class CustomCalculatorExampleWorkflow extends BaseWorkflow<CustomCalculatorExampleArgs> {
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly analyzeTextTool: AnalyzeTextTool,
|
|
23
|
+
private readonly quotaClient: QuotaClientService,
|
|
24
|
+
) {
|
|
25
|
+
super();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@Transition({ to: 'end' })
|
|
29
|
+
async analyze(_state: object, ctx: RunContext<CustomCalculatorExampleArgs>) {
|
|
30
|
+
const result = await this.analyzeTextTool.call({ text: ctx.args.text });
|
|
31
|
+
const analysis = result.data as AnalyzeTextToolResult;
|
|
32
|
+
|
|
33
|
+
const quota = await this.quotaClient.checkQuota(ctx.userId, 'words-processed');
|
|
34
|
+
|
|
35
|
+
await this.documentStore.save(MarkdownDocument, {
|
|
36
|
+
markdown: [
|
|
37
|
+
'## Text Analysis',
|
|
38
|
+
'',
|
|
39
|
+
`- Words: \`${analysis.words}\``,
|
|
40
|
+
`- Characters: \`${analysis.characters}\``,
|
|
41
|
+
'',
|
|
42
|
+
'## Quota `words-processed`',
|
|
43
|
+
'',
|
|
44
|
+
`- Used: \`${quota.used}\``,
|
|
45
|
+
`- Limit: \`${quota.limit}\` (\`-1\` = unlimited / quota disabled)`,
|
|
46
|
+
'',
|
|
47
|
+
'The `QuotaInterceptor` charged the word count automatically via `WordsProcessedQuotaCalculator`.',
|
|
48
|
+
'With `QuotaModule.forRoot({ enabled: false })` all quota calls no-op.',
|
|
49
|
+
].join('\n'),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BaseTool, Tool, ToolEnvelope } from '@loopstack/common';
|
|
3
|
+
|
|
4
|
+
const AnalyzeTextSchema = z.object({
|
|
5
|
+
text: z.string(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
type AnalyzeTextArgs = z.infer<typeof AnalyzeTextSchema>;
|
|
9
|
+
|
|
10
|
+
export interface AnalyzeTextToolResult {
|
|
11
|
+
words: number;
|
|
12
|
+
characters: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const AnalyzeTextToolResultSchema = z.strictObject({
|
|
16
|
+
words: z.number(),
|
|
17
|
+
characters: z.number(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
@Tool({
|
|
21
|
+
name: 'analyze_text',
|
|
22
|
+
description: 'Counts words and characters in a text. Its usage is metered by WordsProcessedQuotaCalculator.',
|
|
23
|
+
schema: AnalyzeTextSchema,
|
|
24
|
+
resultSchema: AnalyzeTextToolResultSchema,
|
|
25
|
+
effects: 'none',
|
|
26
|
+
})
|
|
27
|
+
export class AnalyzeTextTool extends BaseTool<AnalyzeTextArgs, object, AnalyzeTextToolResult> {
|
|
28
|
+
protected async handle(args: AnalyzeTextArgs): Promise<ToolEnvelope<AnalyzeTextToolResult>> {
|
|
29
|
+
const words = args.text.split(/\s+/).filter(Boolean).length;
|
|
30
|
+
return Promise.resolve({ data: { words, characters: args.text.length } });
|
|
31
|
+
}
|
|
32
|
+
}
|