@rlynjb/aptkit-core 0.2.0 → 0.3.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 (33) hide show
  1. package/README.md +1 -1
  2. package/dist/src/index.d.ts +1 -1
  3. package/dist/src/index.js +1 -1
  4. package/node_modules/@aptkit/evals/dist/src/index.d.ts +1 -0
  5. package/node_modules/@aptkit/evals/dist/src/index.js +1 -0
  6. package/node_modules/@aptkit/evals/dist/src/rubric-judge.d.ts +67 -0
  7. package/node_modules/@aptkit/evals/dist/src/rubric-judge.js +154 -0
  8. package/node_modules/@aptkit/evals/package.json +3 -0
  9. package/node_modules/@aptkit/runtime/dist/src/index.d.ts +1 -0
  10. package/node_modules/@aptkit/runtime/dist/src/index.js +1 -0
  11. package/node_modules/@aptkit/runtime/dist/src/structured-generation.d.ts +43 -0
  12. package/node_modules/@aptkit/runtime/dist/src/structured-generation.js +109 -0
  13. package/node_modules/@aptkit/workflows/README.md +6 -0
  14. package/node_modules/@aptkit/workflows/dist/src/content-generation-workflow.d.ts +58 -0
  15. package/node_modules/@aptkit/workflows/dist/src/content-generation-workflow.js +93 -0
  16. package/node_modules/@aptkit/workflows/dist/src/index.d.ts +2 -0
  17. package/node_modules/@aptkit/workflows/dist/src/index.js +2 -0
  18. package/node_modules/@aptkit/workflows/dist/src/markdown-sections.d.ts +9 -0
  19. package/node_modules/@aptkit/workflows/dist/src/markdown-sections.js +32 -0
  20. package/node_modules/@aptkit/{provider-synthetic → workflows}/package.json +3 -4
  21. package/package.json +6 -6
  22. package/node_modules/@aptkit/provider-synthetic/dist/src/fixture-data-source.d.ts +0 -18
  23. package/node_modules/@aptkit/provider-synthetic/dist/src/fixture-data-source.js +0 -120
  24. package/node_modules/@aptkit/provider-synthetic/dist/src/index.d.ts +0 -5
  25. package/node_modules/@aptkit/provider-synthetic/dist/src/index.js +0 -5
  26. package/node_modules/@aptkit/provider-synthetic/dist/src/openai-synthetic-data-source.d.ts +0 -22
  27. package/node_modules/@aptkit/provider-synthetic/dist/src/openai-synthetic-data-source.js +0 -181
  28. package/node_modules/@aptkit/provider-synthetic/dist/src/tool-definitions.d.ts +0 -2
  29. package/node_modules/@aptkit/provider-synthetic/dist/src/tool-definitions.js +0 -59
  30. package/node_modules/@aptkit/provider-synthetic/dist/src/tool-registry.d.ts +0 -15
  31. package/node_modules/@aptkit/provider-synthetic/dist/src/tool-registry.js +0 -28
  32. package/node_modules/@aptkit/provider-synthetic/dist/src/types.d.ts +0 -94
  33. package/node_modules/@aptkit/provider-synthetic/dist/src/types.js +0 -1
package/README.md CHANGED
@@ -13,7 +13,7 @@ The package is published to npmjs as `@rlynjb/aptkit-core`.
13
13
  Blooming keeps its existing imports by installing this package through an npm alias:
14
14
 
15
15
  ```sh
16
- npm install @aptkit/core@npm:@rlynjb/aptkit-core@0.2.0
16
+ npm install @aptkit/core@npm:@rlynjb/aptkit-core@0.2.1
17
17
  ```
18
18
 
19
19
  Because the package is public on npmjs, consumers do not need a package registry token.
@@ -3,7 +3,7 @@ export * from '@aptkit/tools';
3
3
  export * from '@aptkit/context';
4
4
  export * from '@aptkit/prompts';
5
5
  export * from '@aptkit/evals';
6
- export * from '@aptkit/provider-synthetic';
6
+ export * from '@aptkit/workflows';
7
7
  export * from '@aptkit/agent-recommendation';
8
8
  export { ANOMALY_MONITORING_CAPABILITY_ID, AnomalyMonitoringAgent, ECOMMERCE_ANOMALY_CATEGORIES, anomalyMonitoringToolPolicy, coverageReport, formatCategoryChecklist, runnableCategories, schemaCapabilities, tryParseAnomalies, validateAnomalies, } from '@aptkit/agent-anomaly-monitoring';
9
9
  export type { Anomaly as MonitoringAnomaly, AnomalyCategory as MonitoringAnomalyCategory, } from '@aptkit/agent-anomaly-monitoring';
package/dist/src/index.js CHANGED
@@ -3,7 +3,7 @@ export * from '@aptkit/tools';
3
3
  export * from '@aptkit/context';
4
4
  export * from '@aptkit/prompts';
5
5
  export * from '@aptkit/evals';
6
- export * from '@aptkit/provider-synthetic';
6
+ export * from '@aptkit/workflows';
7
7
  export * from '@aptkit/agent-recommendation';
8
8
  export { ANOMALY_MONITORING_CAPABILITY_ID, AnomalyMonitoringAgent, ECOMMERCE_ANOMALY_CATEGORIES, anomalyMonitoringToolPolicy, coverageReport, formatCategoryChecklist, runnableCategories, schemaCapabilities, tryParseAnomalies, validateAnomalies, } from '@aptkit/agent-anomaly-monitoring';
9
9
  export { DIAGNOSTIC_INVESTIGATION_CAPABILITY_ID, DiagnosticInvestigationAgent, diagnosticInvestigationToolPolicy, diagnosisConfidence, tryParseDiagnosis, validateDiagnosis, } from '@aptkit/agent-diagnostic-investigation';
@@ -1,3 +1,4 @@
1
1
  export * from './assertions.js';
2
2
  export * from './detection-scorer.js';
3
+ export * from './rubric-judge.js';
3
4
  export * from './structural-diff.js';
@@ -1,3 +1,4 @@
1
1
  export * from './assertions.js';
2
2
  export * from './detection-scorer.js';
3
+ export * from './rubric-judge.js';
3
4
  export * from './structural-diff.js';
@@ -0,0 +1,67 @@
1
+ import { type CapabilityTraceSink, type JsonValidation, type ModelProvider, type StructuredGenerationResult } from '@aptkit/runtime';
2
+ export type RubricScoreLevel = {
3
+ score: number;
4
+ description: string;
5
+ };
6
+ export type RubricDimension = {
7
+ id: string;
8
+ label: string;
9
+ description: string;
10
+ scale: readonly RubricScoreLevel[];
11
+ };
12
+ export type RubricVerdictRule = {
13
+ verdict: string;
14
+ description: string;
15
+ };
16
+ export type RubricCalibrationExample = {
17
+ input: string;
18
+ expected: string;
19
+ };
20
+ export type RubricDefinition = {
21
+ id: string;
22
+ title: string;
23
+ task: string;
24
+ dimensions: readonly RubricDimension[];
25
+ verdicts: readonly RubricVerdictRule[];
26
+ checks?: readonly string[];
27
+ calibrationExamples?: readonly RubricCalibrationExample[];
28
+ };
29
+ export type RubricDimensionScore = {
30
+ score: number;
31
+ reason: string;
32
+ };
33
+ export type RubricJudgment = {
34
+ dimensions: Record<string, RubricDimensionScore>;
35
+ checks?: Record<string, boolean>;
36
+ verdict: string;
37
+ fix: string;
38
+ reasoning?: string;
39
+ };
40
+ export type RubricJudgeInput = {
41
+ subject: string;
42
+ context?: Record<string, string>;
43
+ };
44
+ export type RubricJudgeOptions = {
45
+ model: ModelProvider;
46
+ rubric: RubricDefinition;
47
+ capabilityId?: string;
48
+ maxTokens?: number;
49
+ temperature?: number;
50
+ trace?: CapabilityTraceSink;
51
+ };
52
+ export type RubricJudgeRunOptions = {
53
+ signal?: AbortSignal;
54
+ };
55
+ export declare class RubricJudge {
56
+ private readonly model;
57
+ private readonly rubric;
58
+ private readonly capabilityId;
59
+ private readonly maxTokens;
60
+ private readonly temperature?;
61
+ private readonly trace?;
62
+ constructor(options: RubricJudgeOptions);
63
+ judge(input: RubricJudgeInput, options?: RubricJudgeRunOptions): Promise<StructuredGenerationResult<RubricJudgment>>;
64
+ }
65
+ export declare function buildRubricJudgeSystemPrompt(rubric: RubricDefinition): string;
66
+ export declare function buildRubricJudgeUserPrompt(input: RubricJudgeInput): string;
67
+ export declare function createRubricJudgmentValidator(rubric: RubricDefinition): (value: unknown) => JsonValidation<RubricJudgment>;
@@ -0,0 +1,154 @@
1
+ import { generateStructured, } from '@aptkit/runtime';
2
+ export class RubricJudge {
3
+ model;
4
+ rubric;
5
+ capabilityId;
6
+ maxTokens;
7
+ temperature;
8
+ trace;
9
+ constructor(options) {
10
+ this.model = options.model;
11
+ this.rubric = options.rubric;
12
+ this.capabilityId = options.capabilityId ?? 'rubric-judge';
13
+ this.maxTokens = options.maxTokens ?? 1200;
14
+ this.temperature = options.temperature;
15
+ this.trace = options.trace;
16
+ }
17
+ judge(input, options = {}) {
18
+ return generateStructured({
19
+ capabilityId: this.capabilityId,
20
+ model: this.model,
21
+ system: buildRubricJudgeSystemPrompt(this.rubric),
22
+ userPrompt: buildRubricJudgeUserPrompt(input),
23
+ validate: createRubricJudgmentValidator(this.rubric),
24
+ maxTokens: this.maxTokens,
25
+ temperature: this.temperature,
26
+ trace: this.trace,
27
+ signal: options.signal,
28
+ });
29
+ }
30
+ }
31
+ export function buildRubricJudgeSystemPrompt(rubric) {
32
+ const dimensions = rubric.dimensions
33
+ .map((dimension) => {
34
+ const scale = dimension.scale
35
+ .map((level) => ` ${level.score} = ${level.description}`)
36
+ .join('\n');
37
+ return `${dimension.id} ${dimension.label}: ${dimension.description}\n${scale}`;
38
+ })
39
+ .join('\n\n');
40
+ const verdicts = rubric.verdicts
41
+ .map((rule) => `- ${rule.verdict}: ${rule.description}`)
42
+ .join('\n');
43
+ const checks = rubric.checks?.length
44
+ ? `\nChecks to return as booleans:\n${rubric.checks.map((check) => `- ${check}`).join('\n')}\n`
45
+ : '';
46
+ const examples = rubric.calibrationExamples?.length
47
+ ? `\nCalibration examples. Use these only to anchor the scoring scale; do not repeat them.\n${rubric.calibrationExamples
48
+ .map((example) => `Input:\n${example.input}\nExpected:\n${example.expected}`)
49
+ .join('\n\n')}\n`
50
+ : '';
51
+ const dimensionShape = Object.fromEntries(rubric.dimensions.map((dimension) => [dimension.id, { score: 0, reason: '' }]));
52
+ const checkShape = Object.fromEntries((rubric.checks ?? []).map((check) => [check, true]));
53
+ const outputShape = {
54
+ dimensions: dimensionShape,
55
+ ...(rubric.checks?.length ? { checks: checkShape } : {}),
56
+ verdict: rubric.verdicts[0]?.verdict ?? 'pass',
57
+ fix: '',
58
+ reasoning: '',
59
+ };
60
+ return [
61
+ `You are a rubric judge for: ${rubric.title}.`,
62
+ rubric.task,
63
+ '',
64
+ 'Score the subject against the rubric. Score meaning and evidence, not style preferences unless the rubric asks for style.',
65
+ 'Never rewrite the subject. Return one highest-leverage fix, not a list.',
66
+ '',
67
+ 'Rubric dimensions:',
68
+ dimensions,
69
+ '',
70
+ 'Allowed verdicts:',
71
+ verdicts,
72
+ checks.trimEnd(),
73
+ examples.trimEnd(),
74
+ '',
75
+ 'Output JSON only. No prose. No markdown fences. Use exactly this shape:',
76
+ JSON.stringify(outputShape),
77
+ ].filter(Boolean).join('\n');
78
+ }
79
+ export function buildRubricJudgeUserPrompt(input) {
80
+ const context = input.context && Object.keys(input.context).length > 0
81
+ ? `Context:\n${Object.entries(input.context).map(([key, value]) => `${key}: ${value}`).join('\n')}\n\n`
82
+ : '';
83
+ return `${context}Subject:\n${input.subject}`;
84
+ }
85
+ export function createRubricJudgmentValidator(rubric) {
86
+ const dimensionIds = new Set(rubric.dimensions.map((dimension) => dimension.id));
87
+ const verdicts = new Set(rubric.verdicts.map((rule) => rule.verdict));
88
+ const scoreRanges = new Map(rubric.dimensions.map((dimension) => [
89
+ dimension.id,
90
+ {
91
+ min: Math.min(...dimension.scale.map((level) => level.score)),
92
+ max: Math.max(...dimension.scale.map((level) => level.score)),
93
+ },
94
+ ]));
95
+ return (value) => {
96
+ if (!isRecord(value))
97
+ return { ok: false, error: 'judgment must be an object' };
98
+ if (!isRecord(value.dimensions))
99
+ return { ok: false, error: 'judgment.dimensions must be an object' };
100
+ const dimensions = {};
101
+ for (const id of dimensionIds) {
102
+ const score = value.dimensions[id];
103
+ if (!isRecord(score))
104
+ return { ok: false, error: `dimensions.${id} must be an object` };
105
+ if (typeof score.score !== 'number')
106
+ return { ok: false, error: `dimensions.${id}.score must be a number` };
107
+ if (typeof score.reason !== 'string')
108
+ return { ok: false, error: `dimensions.${id}.reason must be a string` };
109
+ const range = scoreRanges.get(id);
110
+ if (range && (score.score < range.min || score.score > range.max)) {
111
+ return { ok: false, error: `dimensions.${id}.score must be between ${range.min} and ${range.max}` };
112
+ }
113
+ dimensions[id] = { score: score.score, reason: score.reason.trim() };
114
+ }
115
+ if (typeof value.verdict !== 'string' || !verdicts.has(value.verdict)) {
116
+ return { ok: false, error: 'judgment.verdict is not allowed by the rubric' };
117
+ }
118
+ if (typeof value.fix !== 'string')
119
+ return { ok: false, error: 'judgment.fix must be a string' };
120
+ if (value.reasoning !== undefined && typeof value.reasoning !== 'string') {
121
+ return { ok: false, error: 'judgment.reasoning must be a string when present' };
122
+ }
123
+ const checks = validateChecks(value.checks, rubric.checks);
124
+ if (!checks.ok)
125
+ return checks;
126
+ return {
127
+ ok: true,
128
+ value: {
129
+ dimensions,
130
+ ...(checks.value ? { checks: checks.value } : {}),
131
+ verdict: value.verdict,
132
+ fix: value.fix.trim(),
133
+ ...(value.reasoning ? { reasoning: value.reasoning.trim() } : {}),
134
+ },
135
+ };
136
+ };
137
+ }
138
+ function validateChecks(value, expectedChecks) {
139
+ if (!expectedChecks?.length)
140
+ return { ok: true, value: undefined };
141
+ if (!isRecord(value))
142
+ return { ok: false, error: 'judgment.checks must be an object' };
143
+ const checks = {};
144
+ for (const check of expectedChecks) {
145
+ if (typeof value[check] !== 'boolean') {
146
+ return { ok: false, error: `checks.${check} must be a boolean` };
147
+ }
148
+ checks[check] = value[check];
149
+ }
150
+ return { ok: true, value: checks };
151
+ }
152
+ function isRecord(value) {
153
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
154
+ }
@@ -21,6 +21,9 @@
21
21
  "build": "tsc -p tsconfig.json",
22
22
  "test": "npm run build && node --test dist/test/*.test.js"
23
23
  },
24
+ "dependencies": {
25
+ "@aptkit/runtime": "0.0.0"
26
+ },
24
27
  "devDependencies": {
25
28
  "@types/node": "^20.0.0"
26
29
  }
@@ -3,4 +3,5 @@ export * from './json-output.js';
3
3
  export * from './model-provider.js';
4
4
  export * from './ndjson-stream.js';
5
5
  export * from './run-agent-loop.js';
6
+ export * from './structured-generation.js';
6
7
  export * from './usage-ledger.js';
@@ -3,4 +3,5 @@ export * from './json-output.js';
3
3
  export * from './model-provider.js';
4
4
  export * from './ndjson-stream.js';
5
5
  export * from './run-agent-loop.js';
6
+ export * from './structured-generation.js';
6
7
  export * from './usage-ledger.js';
@@ -0,0 +1,43 @@
1
+ import { type CapabilityTraceSink } from './events.js';
2
+ import { type JsonValidator } from './json-output.js';
3
+ import type { ModelMessage, ModelProvider } from './model-provider.js';
4
+ export type StructuredGenerationAttempt = {
5
+ attempt: number;
6
+ rawText?: string;
7
+ error?: string;
8
+ };
9
+ export type StructuredGenerationSuccess<T> = {
10
+ ok: true;
11
+ value: T;
12
+ rawText: string;
13
+ attempts: StructuredGenerationAttempt[];
14
+ };
15
+ export type StructuredGenerationFailure = {
16
+ ok: false;
17
+ error: string;
18
+ attempts: StructuredGenerationAttempt[];
19
+ };
20
+ export type StructuredGenerationResult<T> = StructuredGenerationSuccess<T> | StructuredGenerationFailure;
21
+ export type StructuredGenerationRetryOptions = {
22
+ maxAttempts?: number;
23
+ strictSuffix?: string;
24
+ };
25
+ export type GenerateStructuredOptions<T> = {
26
+ capabilityId: string;
27
+ model: ModelProvider;
28
+ validate: JsonValidator<T>;
29
+ system?: string;
30
+ messages?: ModelMessage[];
31
+ userPrompt?: string;
32
+ maxTokens?: number;
33
+ temperature?: number;
34
+ retry?: StructuredGenerationRetryOptions;
35
+ trace?: CapabilityTraceSink;
36
+ signal?: AbortSignal;
37
+ };
38
+ /**
39
+ * Runs a JSON-producing model prompt with bounded parse/validation retries.
40
+ * This is the provider-neutral version of Dryrun's on-device JSON pipeline:
41
+ * generate, extract JSON, validate, retry once with a strict JSON-only nudge.
42
+ */
43
+ export declare function generateStructured<T>(options: GenerateStructuredOptions<T>): Promise<StructuredGenerationResult<T>>;
@@ -0,0 +1,109 @@
1
+ import { timestamp } from './events.js';
2
+ import { parseValidatedJson } from './json-output.js';
3
+ const DEFAULT_STRICT_SUFFIX = '\n\nReturn ONLY valid JSON - no prose, no markdown fences.';
4
+ /**
5
+ * Runs a JSON-producing model prompt with bounded parse/validation retries.
6
+ * This is the provider-neutral version of Dryrun's on-device JSON pipeline:
7
+ * generate, extract JSON, validate, retry once with a strict JSON-only nudge.
8
+ */
9
+ export async function generateStructured(options) {
10
+ const maxAttempts = Math.max(1, options.retry?.maxAttempts ?? 2);
11
+ const strictSuffix = options.retry?.strictSuffix ?? DEFAULT_STRICT_SUFFIX;
12
+ const baseMessages = normalizeMessages(options);
13
+ const attempts = [];
14
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
15
+ options.signal?.throwIfAborted();
16
+ const messages = attempt === 1 ? baseMessages : appendStrictSuffix(baseMessages, strictSuffix);
17
+ let response;
18
+ try {
19
+ response = await options.model.complete({
20
+ system: options.system,
21
+ messages,
22
+ maxTokens: options.maxTokens,
23
+ temperature: options.temperature,
24
+ signal: options.signal,
25
+ });
26
+ }
27
+ catch (error) {
28
+ if (isAbortError(error) || options.signal?.aborted)
29
+ throw error;
30
+ const message = error instanceof Error ? error.message : String(error);
31
+ attempts.push({ attempt, error: message });
32
+ emitWarning(options, `structured generation model call failed on attempt ${attempt}: ${message}`);
33
+ return { ok: false, error: message, attempts };
34
+ }
35
+ emitUsage(options, response);
36
+ const rawText = textFromResponse(response);
37
+ const parsed = parseValidatedJson(rawText, options.validate);
38
+ if (parsed.ok) {
39
+ attempts.push({ attempt, rawText });
40
+ return { ok: true, value: parsed.value, rawText, attempts };
41
+ }
42
+ attempts.push({ attempt, rawText, error: parsed.error });
43
+ if (attempt < maxAttempts) {
44
+ emitWarning(options, `structured generation validation failed on attempt ${attempt}: ${parsed.error}`);
45
+ }
46
+ }
47
+ const error = attempts[attempts.length - 1]?.error ?? 'structured generation failed';
48
+ emitError(options, `structured generation failed after ${maxAttempts} attempt${maxAttempts === 1 ? '' : 's'}: ${error}`);
49
+ return { ok: false, error, attempts };
50
+ }
51
+ function normalizeMessages(options) {
52
+ if (options.messages?.length)
53
+ return [...options.messages];
54
+ if (options.userPrompt !== undefined)
55
+ return [{ role: 'user', content: options.userPrompt }];
56
+ throw new Error('generateStructured requires messages or userPrompt');
57
+ }
58
+ function appendStrictSuffix(messages, strictSuffix) {
59
+ const next = [...messages];
60
+ for (let index = next.length - 1; index >= 0; index -= 1) {
61
+ const message = next[index];
62
+ if (message.role === 'user' && typeof message.content === 'string') {
63
+ next[index] = { ...message, content: `${message.content}${strictSuffix}` };
64
+ return next;
65
+ }
66
+ }
67
+ next.push({ role: 'user', content: strictSuffix.trim() });
68
+ return next;
69
+ }
70
+ function textFromResponse(response) {
71
+ return response.content
72
+ .filter((block) => block.type === 'text')
73
+ .map((block) => block.text)
74
+ .join('');
75
+ }
76
+ function emitUsage(options, response) {
77
+ if (!response.usage)
78
+ return;
79
+ options.trace?.emit({
80
+ type: 'model_usage',
81
+ capabilityId: options.capabilityId,
82
+ provider: options.model.id,
83
+ model: response.model ?? options.model.defaultModel ?? 'unknown',
84
+ inputTokens: response.usage.inputTokens,
85
+ outputTokens: response.usage.outputTokens,
86
+ estimated: response.usage.estimated,
87
+ timestamp: timestamp(),
88
+ });
89
+ }
90
+ function emitWarning(options, message) {
91
+ options.trace?.emit({
92
+ type: 'warning',
93
+ capabilityId: options.capabilityId,
94
+ message,
95
+ timestamp: timestamp(),
96
+ });
97
+ }
98
+ function emitError(options, message) {
99
+ options.trace?.emit({
100
+ type: 'error',
101
+ capabilityId: options.capabilityId,
102
+ message,
103
+ timestamp: timestamp(),
104
+ });
105
+ }
106
+ function isAbortError(error) {
107
+ return error instanceof DOMException && error.name === 'AbortError'
108
+ || error instanceof Error && error.name === 'AbortError';
109
+ }
@@ -0,0 +1,6 @@
1
+ # @aptkit/workflows
2
+
3
+ Reusable workflow orchestration helpers for AptKit capabilities.
4
+
5
+ This package owns deterministic workflow mechanics only. Host apps provide
6
+ storage, domain prompts, model providers, and output card shapes.
@@ -0,0 +1,58 @@
1
+ import { type CapabilityTraceSink } from '@aptkit/runtime';
2
+ import { type MarkdownSection } from './markdown-sections.js';
3
+ export type ContentAngle = {
4
+ id: string;
5
+ label: string;
6
+ };
7
+ export type ExistingContentVariant = {
8
+ sourceHash: string;
9
+ variantIndex: number;
10
+ };
11
+ export type ContentVariantPlan = {
12
+ sourceHash: string;
13
+ variantIndex: number;
14
+ sectionIndex: number;
15
+ totalSections: number;
16
+ section: MarkdownSection;
17
+ angle: ContentAngle;
18
+ };
19
+ export type GeneratedContentVariant<T> = ContentVariantPlan & {
20
+ item: T;
21
+ };
22
+ export type ContentGenerator<T> = (plan: ContentVariantPlan, options?: {
23
+ signal?: AbortSignal;
24
+ trace?: CapabilityTraceSink;
25
+ }) => Promise<T | null>;
26
+ export type EnsureGeneratedContentOptions<TExisting extends ExistingContentVariant, TGenerated> = {
27
+ capabilityId?: string;
28
+ sourceMarkdown: string;
29
+ sourceHash: string;
30
+ existing: readonly TExisting[];
31
+ targetCount?: number;
32
+ angles: readonly ContentAngle[];
33
+ maxSkips?: number;
34
+ generator: ContentGenerator<TGenerated>;
35
+ trace?: CapabilityTraceSink;
36
+ signal?: AbortSignal;
37
+ };
38
+ export type EnsureGeneratedContentResult<TExisting extends ExistingContentVariant, TGenerated> = {
39
+ freshExisting: TExisting[];
40
+ staleExisting: TExisting[];
41
+ generated: GeneratedContentVariant<TGenerated>[];
42
+ items: Array<TExisting | GeneratedContentVariant<TGenerated>>;
43
+ attempted: ContentVariantPlan[];
44
+ skipped: ContentVariantPlan[];
45
+ };
46
+ /**
47
+ * Ensures a source document has enough generated variants for the current hash.
48
+ *
49
+ * The host owns persistence. This helper returns fresh existing variants,
50
+ * stale variants to invalidate, and newly generated variants to save.
51
+ */
52
+ export declare function ensureGeneratedContent<TExisting extends ExistingContentVariant, TGenerated>(options: EnsureGeneratedContentOptions<TExisting, TGenerated>): Promise<EnsureGeneratedContentResult<TExisting, TGenerated>>;
53
+ export declare function planContentVariant(options: {
54
+ sourceHash: string;
55
+ variantIndex: number;
56
+ sections: readonly MarkdownSection[];
57
+ angles: readonly ContentAngle[];
58
+ }): ContentVariantPlan;
@@ -0,0 +1,93 @@
1
+ import { timestamp } from '@aptkit/runtime';
2
+ import { splitMarkdownSections } from './markdown-sections.js';
3
+ const DEFAULT_TARGET_COUNT = 4;
4
+ const DEFAULT_MAX_SKIPS = 3;
5
+ /**
6
+ * Ensures a source document has enough generated variants for the current hash.
7
+ *
8
+ * The host owns persistence. This helper returns fresh existing variants,
9
+ * stale variants to invalidate, and newly generated variants to save.
10
+ */
11
+ export async function ensureGeneratedContent(options) {
12
+ options.signal?.throwIfAborted();
13
+ if (options.angles.length === 0)
14
+ throw new Error('ensureGeneratedContent requires at least one angle');
15
+ const capabilityId = options.capabilityId ?? 'content-generation-workflow';
16
+ const targetCount = Math.max(0, options.targetCount ?? DEFAULT_TARGET_COUNT);
17
+ const maxSkips = Math.max(0, options.maxSkips ?? DEFAULT_MAX_SKIPS);
18
+ const sections = splitMarkdownSections(options.sourceMarkdown);
19
+ const freshExisting = options.existing
20
+ .filter((item) => item.sourceHash === options.sourceHash)
21
+ .sort((a, b) => a.variantIndex - b.variantIndex);
22
+ const staleExisting = options.existing
23
+ .filter((item) => item.sourceHash !== options.sourceHash)
24
+ .sort((a, b) => a.variantIndex - b.variantIndex);
25
+ if (targetCount === 0 || sections.length === 0 || freshExisting.length >= targetCount) {
26
+ return {
27
+ freshExisting,
28
+ staleExisting,
29
+ generated: [],
30
+ items: freshExisting,
31
+ attempted: [],
32
+ skipped: [],
33
+ };
34
+ }
35
+ const needed = targetCount - freshExisting.length;
36
+ const baseIndex = (freshExisting.at(-1)?.variantIndex ?? -1) + 1;
37
+ const lastIndex = baseIndex + needed + maxSkips;
38
+ const generated = [];
39
+ const attempted = [];
40
+ const skipped = [];
41
+ for (let variantIndex = baseIndex; generated.length < needed && variantIndex < lastIndex; variantIndex += 1) {
42
+ options.signal?.throwIfAborted();
43
+ const plan = planContentVariant({
44
+ sourceHash: options.sourceHash,
45
+ variantIndex,
46
+ sections,
47
+ angles: options.angles,
48
+ });
49
+ attempted.push(plan);
50
+ options.trace?.emit({
51
+ type: 'step',
52
+ capabilityId,
53
+ role: 'workflow',
54
+ content: `generating ${plan.angle.label} for section ${plan.sectionIndex + 1} of ${plan.totalSections}`,
55
+ timestamp: timestamp(),
56
+ });
57
+ const item = await options.generator(plan, { signal: options.signal, trace: options.trace });
58
+ if (item === null) {
59
+ skipped.push(plan);
60
+ options.trace?.emit({
61
+ type: 'warning',
62
+ capabilityId,
63
+ message: `content variant ${variantIndex} produced no usable output; trying next variant`,
64
+ timestamp: timestamp(),
65
+ });
66
+ continue;
67
+ }
68
+ generated.push({ ...plan, item });
69
+ }
70
+ return {
71
+ freshExisting,
72
+ staleExisting,
73
+ generated,
74
+ items: [...freshExisting, ...generated],
75
+ attempted,
76
+ skipped,
77
+ };
78
+ }
79
+ export function planContentVariant(options) {
80
+ if (options.sections.length === 0)
81
+ throw new Error('planContentVariant requires at least one section');
82
+ if (options.angles.length === 0)
83
+ throw new Error('planContentVariant requires at least one angle');
84
+ const sectionIndex = options.variantIndex % options.sections.length;
85
+ return {
86
+ sourceHash: options.sourceHash,
87
+ variantIndex: options.variantIndex,
88
+ sectionIndex,
89
+ totalSections: options.sections.length,
90
+ section: options.sections[sectionIndex],
91
+ angle: options.angles[options.variantIndex % options.angles.length],
92
+ };
93
+ }
@@ -0,0 +1,2 @@
1
+ export * from './content-generation-workflow.js';
2
+ export * from './markdown-sections.js';
@@ -0,0 +1,2 @@
1
+ export * from './content-generation-workflow.js';
2
+ export * from './markdown-sections.js';
@@ -0,0 +1,9 @@
1
+ export type MarkdownSection = {
2
+ heading?: string;
3
+ content: string;
4
+ };
5
+ /**
6
+ * Splits markdown into ordered sections by h2 (`##`) headings. H3+ headings
7
+ * stay inside the current section, matching Dryrun's source-document workflow.
8
+ */
9
+ export declare function splitMarkdownSections(markdown: string): MarkdownSection[];
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Splits markdown into ordered sections by h2 (`##`) headings. H3+ headings
3
+ * stay inside the current section, matching Dryrun's source-document workflow.
4
+ */
5
+ export function splitMarkdownSections(markdown) {
6
+ if (markdown.trim().length === 0)
7
+ return [];
8
+ const sections = [];
9
+ let heading;
10
+ let body = [];
11
+ function flush() {
12
+ const content = body.join('\n').trim();
13
+ if (content.length > 0 || heading !== undefined) {
14
+ sections.push(heading === undefined ? { content } : { heading, content });
15
+ }
16
+ body = [];
17
+ }
18
+ for (const line of markdown.split(/\r?\n/)) {
19
+ const trimmed = line.trimStart();
20
+ if (trimmed.startsWith('## ') && !trimmed.startsWith('### ')) {
21
+ flush();
22
+ heading = trimmed.slice(3).trim();
23
+ }
24
+ else {
25
+ body.push(line);
26
+ }
27
+ }
28
+ flush();
29
+ if (sections.length === 0)
30
+ return [{ content: markdown.trim() }];
31
+ return sections;
32
+ }
@@ -1,10 +1,11 @@
1
1
  {
2
- "name": "@aptkit/provider-synthetic",
2
+ "name": "@aptkit/workflows",
3
3
  "version": "0.0.0",
4
4
  "type": "module",
5
5
  "main": "./dist/src/index.js",
6
6
  "types": "./dist/src/index.d.ts",
7
7
  "files": [
8
+ "README.md",
8
9
  "dist/src"
9
10
  ],
10
11
  "exports": {
@@ -18,9 +19,7 @@
18
19
  "test": "npm run build && node --test dist/test/*.test.js"
19
20
  },
20
21
  "dependencies": {
21
- "@aptkit/context": "0.0.0",
22
- "@aptkit/runtime": "0.0.0",
23
- "@aptkit/tools": "0.0.0"
22
+ "@aptkit/runtime": "0.0.0"
24
23
  },
25
24
  "devDependencies": {
26
25
  "@types/node": "^20.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rlynjb/aptkit-core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Standalone AptKit core bundle for extracted agent capabilities.",
6
6
  "main": "./dist/src/index.js",
@@ -25,7 +25,7 @@
25
25
  }
26
26
  },
27
27
  "scripts": {
28
- "build": "tsc -p tsconfig.json",
28
+ "build": "tsc -b tsconfig.json",
29
29
  "test": "npm run build && node --test dist/test/*.test.js"
30
30
  },
31
31
  "dependencies": {
@@ -36,9 +36,9 @@
36
36
  "@aptkit/context": "0.0.0",
37
37
  "@aptkit/evals": "0.0.0",
38
38
  "@aptkit/prompts": "0.0.0",
39
- "@aptkit/provider-synthetic": "0.0.0",
40
39
  "@aptkit/runtime": "0.0.0",
41
- "@aptkit/tools": "0.0.0"
40
+ "@aptkit/tools": "0.0.0",
41
+ "@aptkit/workflows": "0.0.0"
42
42
  },
43
43
  "bundledDependencies": [
44
44
  "@aptkit/agent-anomaly-monitoring",
@@ -48,8 +48,8 @@
48
48
  "@aptkit/context",
49
49
  "@aptkit/evals",
50
50
  "@aptkit/prompts",
51
- "@aptkit/provider-synthetic",
52
51
  "@aptkit/runtime",
53
- "@aptkit/tools"
52
+ "@aptkit/tools",
53
+ "@aptkit/workflows"
54
54
  ]
55
55
  }
@@ -1,18 +0,0 @@
1
- import type { WorkspaceDescriptor } from '@aptkit/context';
2
- import type { AnomalyContextArgs, AnomalyContextResult, MetricTimeseriesArgs, MetricTimeseriesResult, ProjectOverview, SyntheticEcommerceDataSource } from './types.js';
3
- export declare const syntheticEcommerceWorkspace: WorkspaceDescriptor;
4
- export type FixtureSyntheticEcommerceDataSourceOptions = {
5
- scenarioId?: string;
6
- workspace?: WorkspaceDescriptor;
7
- };
8
- /** Deterministic synthetic ecommerce data source for Studio, tests, and repeatable demos. */
9
- export declare class FixtureSyntheticEcommerceDataSource implements SyntheticEcommerceDataSource {
10
- readonly mode: "fixture";
11
- readonly scenarioId: string;
12
- readonly workspace: WorkspaceDescriptor;
13
- constructor(options?: FixtureSyntheticEcommerceDataSourceOptions);
14
- getProjectOverview(): ProjectOverview;
15
- getMetricTimeseries(args?: MetricTimeseriesArgs): MetricTimeseriesResult;
16
- getAnomalyContext(args?: AnomalyContextArgs): AnomalyContextResult;
17
- private providerMetadata;
18
- }
@@ -1,120 +0,0 @@
1
- const DEFAULT_RECENT_WINDOW = { from: '2026-05-04', to: '2026-06-01' };
2
- const DEFAULT_BASELINE_WINDOW = { from: '2026-02-09', to: '2026-05-04' };
3
- export const syntheticEcommerceWorkspace = {
4
- projectId: 'synthetic-ecommerce',
5
- projectName: 'Synthetic ecommerce analytics workspace',
6
- events: [
7
- { name: 'purchase', properties: ['state', 'category', 'payment_type', 'total_price'], eventCount: 50000 },
8
- { name: 'session_start', properties: ['utm_source', 'device'], eventCount: 80000 },
9
- { name: 'checkout', properties: ['state', 'payment_type'], eventCount: 35000 },
10
- { name: 'view_item', properties: ['category'], eventCount: 120000 },
11
- ],
12
- customerProperties: ['state', 'city', 'loyalty_tier'],
13
- catalogs: [{ id: 'products', name: 'Products' }],
14
- totalCustomers: 125000,
15
- totalEvents: 285000,
16
- oldestTimestamp: Date.UTC(2025, 11, 1),
17
- dataHorizon: { from: '2025-12-01', to: '2026-06-01', durationDays: 182 },
18
- };
19
- /** Deterministic synthetic ecommerce data source for Studio, tests, and repeatable demos. */
20
- export class FixtureSyntheticEcommerceDataSource {
21
- mode = 'fixture';
22
- scenarioId;
23
- workspace;
24
- constructor(options = {}) {
25
- this.scenarioId = options.scenarioId ?? 'sp-revenue-drop';
26
- this.workspace = options.workspace ?? syntheticEcommerceWorkspace;
27
- }
28
- getProjectOverview() {
29
- return {
30
- provider: this.providerMetadata(),
31
- workspace: this.workspace,
32
- highlights: [
33
- 'SP revenue is down 30.0245% over the recent four-week window.',
34
- 'RJ and MG remain approximately flat, making the movement region-specific.',
35
- 'The fixture is deterministic and safe for replay promotion.',
36
- ],
37
- };
38
- }
39
- getMetricTimeseries(args = {}) {
40
- const metric = args.metric ?? 'revenue';
41
- const dimension = args.dimension ?? 'state';
42
- const segment = args.segment ?? 'SP';
43
- const recentWindow = requiredRange(args.time_range, DEFAULT_RECENT_WINDOW);
44
- return {
45
- provider: this.providerMetadata(),
46
- periodComparison: {
47
- metric,
48
- dimension,
49
- segment,
50
- recentWindow,
51
- baselineWindow: DEFAULT_BASELINE_WINDOW,
52
- recentValue: 28550000,
53
- baselineAverage: 40800000,
54
- pctChange: -0.300245,
55
- relatedSegments: [
56
- { name: 'RJ', pctChange: -0.02 },
57
- { name: 'MG', pctChange: 0.01 },
58
- ],
59
- },
60
- points: [
61
- { ts: '2026-05-04', segment: 'SP', value: 7400000 },
62
- { ts: '2026-05-11', segment: 'SP', value: 7200000 },
63
- { ts: '2026-05-18', segment: 'SP', value: 7050000 },
64
- { ts: '2026-05-25', segment: 'SP', value: 6900000 },
65
- { ts: '2026-05-04', segment: 'RJ', value: 4300000 },
66
- { ts: '2026-05-11', segment: 'RJ', value: 4350000 },
67
- { ts: '2026-05-04', segment: 'MG', value: 3900000 },
68
- { ts: '2026-05-11', segment: 'MG', value: 3940000 },
69
- ],
70
- totalCount: 4200,
71
- };
72
- }
73
- getAnomalyContext(args = {}) {
74
- const metric = args.metric ?? 'revenue';
75
- const segment = args.segment ?? 'SP';
76
- return {
77
- provider: this.providerMetadata(),
78
- anomaly_summary: {
79
- metric,
80
- segment,
81
- anomaly_value: 28550000,
82
- baseline_avg: 40800000,
83
- pct_change: -0.300245,
84
- },
85
- related_segments: [
86
- { name: 'RJ', pct_change: -0.02 },
87
- { name: 'MG', pct_change: 0.01 },
88
- ],
89
- drivers: [
90
- {
91
- name: 'electronics',
92
- contribution: -0.62,
93
- detail: 'Electronics orders in SP account for most of the recent revenue loss.',
94
- },
95
- {
96
- name: 'paid_search',
97
- contribution: -0.21,
98
- detail: 'Paid search sessions declined during the same window.',
99
- },
100
- ],
101
- sample_orders: [
102
- { order_id: 'syn-sp-101', purchase_ts: '2026-05-10', status: 'delivered', price_brl: 42000 },
103
- { order_id: 'syn-sp-102', purchase_ts: '2026-05-23', status: 'delivered', price_brl: 31000 },
104
- ],
105
- };
106
- }
107
- providerMetadata() {
108
- return {
109
- id: 'synthetic-ecommerce',
110
- mode: this.mode,
111
- scenarioId: this.scenarioId,
112
- };
113
- }
114
- }
115
- function requiredRange(input, fallback) {
116
- return {
117
- from: input?.from ?? fallback.from,
118
- to: input?.to ?? fallback.to,
119
- };
120
- }
@@ -1,5 +0,0 @@
1
- export * from './fixture-data-source.js';
2
- export * from './openai-synthetic-data-source.js';
3
- export * from './tool-definitions.js';
4
- export * from './tool-registry.js';
5
- export * from './types.js';
@@ -1,5 +0,0 @@
1
- export * from './fixture-data-source.js';
2
- export * from './openai-synthetic-data-source.js';
3
- export * from './tool-definitions.js';
4
- export * from './tool-registry.js';
5
- export * from './types.js';
@@ -1,22 +0,0 @@
1
- import { type ModelProvider } from '@aptkit/runtime';
2
- import type { AnomalyContextArgs, AnomalyContextResult, MetricTimeseriesArgs, MetricTimeseriesResult, ProjectOverview, SyntheticEcommerceDataSource } from './types.js';
3
- import type { WorkspaceDescriptor } from '@aptkit/context';
4
- export type OpenAISyntheticEcommerceDataSourceOptions = {
5
- model: ModelProvider;
6
- scenarioId?: string;
7
- workspace?: WorkspaceDescriptor;
8
- systemPrompt?: string;
9
- };
10
- /** Model-backed synthetic data source. Use with OpenAIModelProvider or any ModelProvider adapter. */
11
- export declare class OpenAISyntheticEcommerceDataSource implements SyntheticEcommerceDataSource {
12
- readonly mode: "openai";
13
- readonly scenarioId: string;
14
- readonly workspace: WorkspaceDescriptor;
15
- private readonly model;
16
- private readonly systemPrompt;
17
- constructor(options: OpenAISyntheticEcommerceDataSourceOptions);
18
- getProjectOverview(): Promise<ProjectOverview>;
19
- getMetricTimeseries(args?: MetricTimeseriesArgs): Promise<MetricTimeseriesResult>;
20
- getAnomalyContext(args?: AnomalyContextArgs): Promise<AnomalyContextResult>;
21
- private completeJson;
22
- }
@@ -1,181 +0,0 @@
1
- import { parseAgentJson } from '@aptkit/runtime';
2
- import { syntheticEcommerceWorkspace } from './fixture-data-source.js';
3
- /** Model-backed synthetic data source. Use with OpenAIModelProvider or any ModelProvider adapter. */
4
- export class OpenAISyntheticEcommerceDataSource {
5
- mode = 'openai';
6
- scenarioId;
7
- workspace;
8
- model;
9
- systemPrompt;
10
- constructor(options) {
11
- this.model = options.model;
12
- this.scenarioId = options.scenarioId ?? 'sp-revenue-drop';
13
- this.workspace = options.workspace ?? syntheticEcommerceWorkspace;
14
- this.systemPrompt = options.systemPrompt ?? [
15
- 'You generate synthetic ecommerce analytics tool results for AptKit.',
16
- 'Return only JSON. Do not include prose or markdown fences.',
17
- 'Keep outputs internally consistent for the requested scenario and workspace.',
18
- 'Always include provider metadata: {"id":"synthetic-ecommerce","mode":"openai","scenarioId": string}.',
19
- ].join('\n');
20
- }
21
- async getProjectOverview() {
22
- const value = await this.completeJson('get_project_overview', {});
23
- return assertProjectOverview(value, this.scenarioId);
24
- }
25
- async getMetricTimeseries(args = {}) {
26
- const value = await this.completeJson('get_metric_timeseries', args);
27
- return assertMetricTimeseries(value, this.scenarioId);
28
- }
29
- async getAnomalyContext(args = {}) {
30
- const value = await this.completeJson('get_anomaly_context', args);
31
- return assertAnomalyContext(value, this.scenarioId);
32
- }
33
- async completeJson(toolName, args) {
34
- const response = await this.model.complete({
35
- system: this.systemPrompt,
36
- messages: [
37
- {
38
- role: 'user',
39
- content: JSON.stringify({
40
- toolName,
41
- scenarioId: this.scenarioId,
42
- workspace: this.workspace,
43
- args,
44
- }),
45
- },
46
- ],
47
- maxTokens: 1400,
48
- temperature: 0.2,
49
- });
50
- const text = response.content
51
- .filter((block) => block.type === 'text')
52
- .map((block) => block.text)
53
- .join('\n');
54
- return parseAgentJson(text);
55
- }
56
- }
57
- function assertProjectOverview(value, scenarioId) {
58
- const object = assertObject(value, 'project overview');
59
- if (!isObject(object.workspace))
60
- throw new Error('project overview workspace is missing');
61
- return {
62
- provider: providerMetadata(object.provider, scenarioId, 'openai'),
63
- workspace: object.workspace,
64
- highlights: stringArray(object.highlights, 'highlights'),
65
- };
66
- }
67
- function assertMetricTimeseries(value, scenarioId) {
68
- const object = assertObject(value, 'metric timeseries');
69
- const comparison = assertObject(object.periodComparison, 'periodComparison');
70
- return {
71
- provider: providerMetadata(object.provider, scenarioId, 'openai'),
72
- periodComparison: {
73
- metric: stringValue(comparison.metric, 'periodComparison.metric'),
74
- dimension: stringValue(comparison.dimension, 'periodComparison.dimension'),
75
- segment: stringValue(comparison.segment, 'periodComparison.segment'),
76
- recentWindow: timeRange(comparison.recentWindow, 'periodComparison.recentWindow'),
77
- baselineWindow: timeRange(comparison.baselineWindow, 'periodComparison.baselineWindow'),
78
- recentValue: numberValue(comparison.recentValue, 'periodComparison.recentValue'),
79
- baselineAverage: numberValue(comparison.baselineAverage, 'periodComparison.baselineAverage'),
80
- pctChange: numberValue(comparison.pctChange, 'periodComparison.pctChange'),
81
- relatedSegments: relatedSegments(comparison.relatedSegments),
82
- },
83
- points: metricPoints(object.points),
84
- totalCount: numberValue(object.totalCount, 'totalCount'),
85
- };
86
- }
87
- function assertAnomalyContext(value, scenarioId) {
88
- const object = assertObject(value, 'anomaly context');
89
- const summary = assertObject(object.anomaly_summary, 'anomaly_summary');
90
- return {
91
- provider: providerMetadata(object.provider, scenarioId, 'openai'),
92
- anomaly_summary: {
93
- metric: stringValue(summary.metric, 'anomaly_summary.metric'),
94
- segment: stringValue(summary.segment, 'anomaly_summary.segment'),
95
- anomaly_value: numberValue(summary.anomaly_value, 'anomaly_summary.anomaly_value'),
96
- baseline_avg: numberValue(summary.baseline_avg, 'anomaly_summary.baseline_avg'),
97
- pct_change: numberValue(summary.pct_change, 'anomaly_summary.pct_change'),
98
- },
99
- related_segments: relatedSegments(object.related_segments).map((segment) => ({
100
- name: segment.name,
101
- pct_change: segment.pctChange,
102
- })),
103
- drivers: arrayValue(object.drivers, 'drivers').map((driver, index) => {
104
- const objectDriver = assertObject(driver, `drivers.${index}`);
105
- return {
106
- name: stringValue(objectDriver.name, `drivers.${index}.name`),
107
- contribution: numberValue(objectDriver.contribution, `drivers.${index}.contribution`),
108
- detail: stringValue(objectDriver.detail, `drivers.${index}.detail`),
109
- };
110
- }),
111
- sample_orders: arrayValue(object.sample_orders, 'sample_orders').map((order, index) => {
112
- const objectOrder = assertObject(order, `sample_orders.${index}`);
113
- return {
114
- order_id: stringValue(objectOrder.order_id, `sample_orders.${index}.order_id`),
115
- purchase_ts: stringValue(objectOrder.purchase_ts, `sample_orders.${index}.purchase_ts`),
116
- status: stringValue(objectOrder.status, `sample_orders.${index}.status`),
117
- price_brl: numberValue(objectOrder.price_brl, `sample_orders.${index}.price_brl`),
118
- };
119
- }),
120
- };
121
- }
122
- function providerMetadata(value, scenarioId, mode) {
123
- const provider = isObject(value) ? value : {};
124
- return {
125
- id: 'synthetic-ecommerce',
126
- mode,
127
- scenarioId: typeof provider.scenarioId === 'string' ? provider.scenarioId : scenarioId,
128
- };
129
- }
130
- function relatedSegments(value) {
131
- return arrayValue(value, 'relatedSegments').map((segment, index) => {
132
- const object = assertObject(segment, `relatedSegments.${index}`);
133
- return {
134
- name: stringValue(object.name, `relatedSegments.${index}.name`),
135
- pctChange: numberValue(object.pctChange ?? object.pct_change, `relatedSegments.${index}.pctChange`),
136
- };
137
- });
138
- }
139
- function metricPoints(value) {
140
- return arrayValue(value, 'points').map((point, index) => {
141
- const object = assertObject(point, `points.${index}`);
142
- return {
143
- ts: stringValue(object.ts, `points.${index}.ts`),
144
- segment: stringValue(object.segment, `points.${index}.segment`),
145
- value: numberValue(object.value, `points.${index}.value`),
146
- };
147
- });
148
- }
149
- function timeRange(value, path) {
150
- const object = assertObject(value, path);
151
- return {
152
- from: stringValue(object.from, `${path}.from`),
153
- to: stringValue(object.to, `${path}.to`),
154
- };
155
- }
156
- function assertObject(value, path) {
157
- if (!isObject(value))
158
- throw new Error(`${path} must be an object`);
159
- return value;
160
- }
161
- function isObject(value) {
162
- return typeof value === 'object' && value !== null && !Array.isArray(value);
163
- }
164
- function arrayValue(value, path) {
165
- if (!Array.isArray(value))
166
- throw new Error(`${path} must be an array`);
167
- return value;
168
- }
169
- function stringArray(value, path) {
170
- return arrayValue(value, path).map((item, index) => stringValue(item, `${path}.${index}`));
171
- }
172
- function stringValue(value, path) {
173
- if (typeof value !== 'string')
174
- throw new Error(`${path} must be a string`);
175
- return value;
176
- }
177
- function numberValue(value, path) {
178
- if (typeof value !== 'number' || !Number.isFinite(value))
179
- throw new Error(`${path} must be a number`);
180
- return value;
181
- }
@@ -1,2 +0,0 @@
1
- import type { ToolDefinition } from '@aptkit/tools';
2
- export declare const syntheticEcommerceToolDefinitions: ToolDefinition[];
@@ -1,59 +0,0 @@
1
- export const syntheticEcommerceToolDefinitions = [
2
- {
3
- name: 'get_project_overview',
4
- description: 'Return synthetic ecommerce workspace metadata and data horizon.',
5
- inputSchema: {
6
- type: 'object',
7
- properties: {},
8
- additionalProperties: false,
9
- },
10
- },
11
- {
12
- name: 'get_metric_timeseries',
13
- description: 'Return synthetic ecommerce metric timeseries for an optional dimension and segment.',
14
- inputSchema: {
15
- type: 'object',
16
- properties: {
17
- metric: { type: 'string' },
18
- dimension: { type: 'string' },
19
- segment: { type: 'string' },
20
- time_range: {
21
- type: 'object',
22
- properties: {
23
- from: { type: 'string' },
24
- to: { type: 'string' },
25
- },
26
- },
27
- granularity: { type: 'string' },
28
- },
29
- required: ['metric'],
30
- },
31
- },
32
- {
33
- name: 'get_anomaly_context',
34
- description: 'Return synthetic ecommerce anomaly context, related segments, and sample records.',
35
- inputSchema: {
36
- type: 'object',
37
- properties: {
38
- metric: { type: 'string' },
39
- dimension: { type: 'string' },
40
- segment: { type: 'string' },
41
- anomaly_window: {
42
- type: 'object',
43
- properties: {
44
- from: { type: 'string' },
45
- to: { type: 'string' },
46
- },
47
- },
48
- baseline_window: {
49
- type: 'object',
50
- properties: {
51
- from: { type: 'string' },
52
- to: { type: 'string' },
53
- },
54
- },
55
- },
56
- required: ['metric', 'dimension', 'segment'],
57
- },
58
- },
59
- ];
@@ -1,15 +0,0 @@
1
- import type { ToolCallOptions, ToolCallResult, ToolDefinition, ToolRegistry } from '@aptkit/tools';
2
- import type { SyntheticEcommerceDataSource } from './types.js';
3
- export type SyntheticEcommerceToolRegistryOptions = {
4
- dataSource: SyntheticEcommerceDataSource;
5
- tools?: readonly ToolDefinition[];
6
- };
7
- /** ToolRegistry adapter that exposes synthetic ecommerce data through normal agent tools. */
8
- export declare class SyntheticEcommerceToolRegistry implements ToolRegistry {
9
- private readonly dataSource;
10
- private readonly tools;
11
- constructor(options: SyntheticEcommerceToolRegistryOptions);
12
- listTools(): ToolDefinition[];
13
- callTool(name: string, args: Record<string, unknown>, options?: ToolCallOptions): Promise<ToolCallResult>;
14
- private dispatch;
15
- }
@@ -1,28 +0,0 @@
1
- import { syntheticEcommerceToolDefinitions } from './tool-definitions.js';
2
- /** ToolRegistry adapter that exposes synthetic ecommerce data through normal agent tools. */
3
- export class SyntheticEcommerceToolRegistry {
4
- dataSource;
5
- tools;
6
- constructor(options) {
7
- this.dataSource = options.dataSource;
8
- this.tools = options.tools ?? syntheticEcommerceToolDefinitions;
9
- }
10
- listTools() {
11
- return [...this.tools];
12
- }
13
- async callTool(name, args, options) {
14
- options?.signal?.throwIfAborted();
15
- const start = performance.now();
16
- const result = await this.dispatch(name, args);
17
- return { result, durationMs: Math.round(performance.now() - start) };
18
- }
19
- dispatch(name, args) {
20
- if (name === 'get_project_overview')
21
- return this.dataSource.getProjectOverview();
22
- if (name === 'get_metric_timeseries')
23
- return this.dataSource.getMetricTimeseries(args);
24
- if (name === 'get_anomaly_context')
25
- return this.dataSource.getAnomalyContext(args);
26
- throw new Error(`synthetic ecommerce tool not found: ${name}`);
27
- }
28
- }
@@ -1,94 +0,0 @@
1
- import type { WorkspaceDescriptor } from '@aptkit/context';
2
- export type SyntheticProviderMode = 'fixture' | 'openai';
3
- export type TimeRange = {
4
- from?: string;
5
- to?: string;
6
- };
7
- export type ProjectOverview = {
8
- provider: {
9
- id: 'synthetic-ecommerce';
10
- mode: SyntheticProviderMode;
11
- scenarioId: string;
12
- };
13
- workspace: WorkspaceDescriptor;
14
- highlights: string[];
15
- };
16
- export type MetricTimeseriesArgs = {
17
- metric?: string;
18
- dimension?: string;
19
- segment?: string;
20
- time_range?: TimeRange;
21
- granularity?: 'day' | 'week' | 'month' | string;
22
- };
23
- export type MetricPoint = {
24
- ts: string;
25
- segment: string;
26
- value: number;
27
- };
28
- export type MetricTimeseriesResult = {
29
- provider: {
30
- id: 'synthetic-ecommerce';
31
- mode: SyntheticProviderMode;
32
- scenarioId: string;
33
- };
34
- periodComparison: {
35
- metric: string;
36
- dimension: string;
37
- segment: string;
38
- recentWindow: Required<TimeRange>;
39
- baselineWindow: Required<TimeRange>;
40
- recentValue: number;
41
- baselineAverage: number;
42
- pctChange: number;
43
- relatedSegments: {
44
- name: string;
45
- pctChange: number;
46
- }[];
47
- };
48
- points: MetricPoint[];
49
- totalCount: number;
50
- };
51
- export type AnomalyContextArgs = {
52
- metric?: string;
53
- dimension?: string;
54
- segment?: string;
55
- anomaly_window?: TimeRange;
56
- baseline_window?: TimeRange;
57
- };
58
- export type AnomalyContextResult = {
59
- provider: {
60
- id: 'synthetic-ecommerce';
61
- mode: SyntheticProviderMode;
62
- scenarioId: string;
63
- };
64
- anomaly_summary: {
65
- metric: string;
66
- segment: string;
67
- anomaly_value: number;
68
- baseline_avg: number;
69
- pct_change: number;
70
- };
71
- related_segments: {
72
- name: string;
73
- pct_change: number;
74
- }[];
75
- drivers: {
76
- name: string;
77
- contribution: number;
78
- detail: string;
79
- }[];
80
- sample_orders: {
81
- order_id: string;
82
- purchase_ts: string;
83
- status: string;
84
- price_brl: number;
85
- }[];
86
- };
87
- export type SyntheticEcommerceDataSource = {
88
- readonly mode: SyntheticProviderMode;
89
- readonly scenarioId: string;
90
- readonly workspace: WorkspaceDescriptor;
91
- getProjectOverview(): Promise<ProjectOverview> | ProjectOverview;
92
- getMetricTimeseries(args?: MetricTimeseriesArgs): Promise<MetricTimeseriesResult> | MetricTimeseriesResult;
93
- getAnomalyContext(args?: AnomalyContextArgs): Promise<AnomalyContextResult> | AnomalyContextResult;
94
- };