@plumbus/core 0.6.19 → 0.7.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/CHANGELOG.md +72 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/ai/__typecheck-fixtures__/legacy-cost-api.d.ts +5 -0
- package/dist/ai/__typecheck-fixtures__/legacy-cost-api.d.ts.map +1 -0
- package/dist/ai/__typecheck-fixtures__/legacy-cost-api.js +15 -0
- package/dist/ai/__typecheck-fixtures__/legacy-cost-api.js.map +1 -0
- package/dist/ai/ai/cost-tracker.d.ts +4 -3
- package/dist/ai/ai/model-pricing.d.ts +14 -5
- package/dist/ai/ai/usage-client.d.ts +2 -0
- package/dist/ai/ai/usage-validation.d.ts +3 -0
- package/dist/ai/ai-service.d.ts.map +1 -1
- package/dist/ai/ai-service.js +101 -33
- package/dist/ai/ai-service.js.map +1 -1
- package/dist/ai/cost-tracker.d.ts +4 -3
- package/dist/ai/cost-tracker.d.ts.map +1 -1
- package/dist/ai/cost-tracker.js +33 -14
- package/dist/ai/cost-tracker.js.map +1 -1
- package/dist/ai/errors/data-errors.d.ts +22 -0
- package/dist/ai/errors/index.d.ts +21 -0
- package/dist/ai/errors/plumbus-error.d.ts +12 -0
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +1 -1
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/model-pricing.d.ts +14 -5
- package/dist/ai/model-pricing.d.ts.map +1 -1
- package/dist/ai/model-pricing.js +88 -45
- package/dist/ai/model-pricing.js.map +1 -1
- package/dist/ai/provider.d.ts.map +1 -1
- package/dist/ai/provider.js +22 -9
- package/dist/ai/provider.js.map +1 -1
- package/dist/ai/rag/pipeline.d.ts +2 -0
- package/dist/ai/rag/pipeline.d.ts.map +1 -1
- package/dist/ai/rag/pipeline.js +6 -1
- package/dist/ai/rag/pipeline.js.map +1 -1
- package/dist/ai/security.d.ts.map +1 -1
- package/dist/ai/security.js +1 -0
- package/dist/ai/security.js.map +1 -1
- package/dist/ai/tool-loop.d.ts +1 -0
- package/dist/ai/tool-loop.d.ts.map +1 -1
- package/dist/ai/tool-loop.js +23 -4
- package/dist/ai/tool-loop.js.map +1 -1
- package/dist/ai/types/errors.d.ts +14 -0
- package/dist/ai/usage-client.d.ts +2 -0
- package/dist/ai/usage-client.d.ts.map +1 -1
- package/dist/ai/usage-client.js +2 -0
- package/dist/ai/usage-client.js.map +1 -1
- package/dist/ai/usage-validation.d.ts +4 -0
- package/dist/ai/usage-validation.d.ts.map +1 -0
- package/dist/ai/usage-validation.js +23 -0
- package/dist/ai/usage-validation.js.map +1 -0
- package/dist/ai/validation.d.ts +1 -5
- package/dist/ai/validation.d.ts.map +1 -1
- package/dist/api/cookies.d.ts.map +1 -1
- package/dist/api/cookies.js +7 -2
- package/dist/api/cookies.js.map +1 -1
- package/dist/audit/service.d.ts.map +1 -1
- package/dist/audit/service.js +21 -3
- package/dist/audit/service.js.map +1 -1
- package/dist/audit/writer.d.ts.map +1 -1
- package/dist/audit/writer.js +7 -2
- package/dist/audit/writer.js.map +1 -1
- package/dist/auth/adapter.d.ts +5 -1
- package/dist/auth/adapter.d.ts.map +1 -1
- package/dist/auth/adapter.js +36 -6
- package/dist/auth/adapter.js.map +1 -1
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/jwt-policy.d.ts +3 -0
- package/dist/auth/jwt-policy.d.ts.map +1 -0
- package/dist/auth/jwt-policy.js +11 -0
- package/dist/auth/jwt-policy.js.map +1 -0
- package/dist/auth/oidc-adapter.d.ts.map +1 -1
- package/dist/auth/oidc-adapter.js +30 -13
- package/dist/auth/oidc-adapter.js.map +1 -1
- package/dist/auth/saml-adapter.d.ts +7 -1
- package/dist/auth/saml-adapter.d.ts.map +1 -1
- package/dist/auth/saml-adapter.js +42 -5
- package/dist/auth/saml-adapter.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +21 -0
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +4 -3
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +7 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +4 -3
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/translation.d.ts.map +1 -1
- package/dist/cli/commands/translation.js +8 -4
- package/dist/cli/commands/translation.js.map +1 -1
- package/dist/cli/mcp-serve-context.d.ts.map +1 -1
- package/dist/cli/mcp-serve-context.js +25 -6
- package/dist/cli/mcp-serve-context.js.map +1 -1
- package/dist/cli/scaffold-validation.d.ts +6 -0
- package/dist/cli/scaffold-validation.d.ts.map +1 -0
- package/dist/cli/scaffold-validation.js +57 -0
- package/dist/cli/scaffold-validation.js.map +1 -0
- package/dist/cli/scaffold-write.d.ts.map +1 -1
- package/dist/cli/scaffold-write.js +13 -3
- package/dist/cli/scaffold-write.js.map +1 -1
- package/dist/cli/templates/resources.d.ts.map +1 -1
- package/dist/cli/templates/resources.js +10 -0
- package/dist/cli/templates/resources.js.map +1 -1
- package/dist/cli/utils.d.ts +1 -1
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js +13 -2
- package/dist/cli/utils.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +7 -4
- package/dist/config/loader.js.map +1 -1
- package/dist/errors/http.d.ts.map +1 -1
- package/dist/errors/http.js +8 -3
- package/dist/errors/http.js.map +1 -1
- package/dist/events/queue.d.ts.map +1 -1
- package/dist/events/queue.js +27 -4
- package/dist/events/queue.js.map +1 -1
- package/dist/execution/capability-executor.js +2 -1
- package/dist/execution/capability-executor.js.map +1 -1
- package/dist/execution/context-factory.d.ts.map +1 -1
- package/dist/execution/context-factory.js +6 -2
- package/dist/execution/context-factory.js.map +1 -1
- package/dist/flows/engine.d.ts.map +1 -1
- package/dist/flows/engine.js +28 -18
- package/dist/flows/engine.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/start-worker-pool.d.ts.map +1 -1
- package/dist/runtime/start-worker-pool.js +21 -9
- package/dist/runtime/start-worker-pool.js.map +1 -1
- package/dist/server/bootstrap.d.ts.map +1 -1
- package/dist/server/bootstrap.js +13 -9
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/testing/e2e.js +1 -1
- package/dist/testing/e2e.js.map +1 -1
- package/dist/types/audit.d.ts +2 -0
- package/dist/types/audit.d.ts.map +1 -1
- package/dist/types/context.d.ts +11 -1
- package/dist/types/context.d.ts.map +1 -1
- package/instructions/README.md +1 -0
- package/instructions/ai.md +10 -2
- package/instructions/deployment.md +5 -2
- package/instructions/flows.md +3 -0
- package/instructions/framework.md +7 -0
- package/instructions/mcp.md +5 -2
- package/instructions/peer-dependencies.md +28 -64
- package/instructions/security.md +8 -1
- package/instructions/upgrading-0.5-capabilities.md +4 -1
- package/instructions/upgrading-security-release.md +65 -0
- package/package.json +9 -9
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type AIService, type RAGPipelineConfig, type RunToolLoopParams } from '@plumbus/core';
|
|
2
|
+
export declare function legacyConsumer(ai: AIService, params: RunToolLoopParams): Promise<number>;
|
|
3
|
+
export declare const legacyEmbeddingCallback: RAGPipelineConfig['onEmbeddingCost'];
|
|
4
|
+
export declare const uncheckedEstimate: number;
|
|
5
|
+
//# sourceMappingURL=legacy-cost-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-cost-api.d.ts","sourceRoot":"","sources":["../../../src/ai/__typecheck-fixtures__/legacy-cost-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAC;AAEvB,wBAAsB,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9F;AAED,eAAO,MAAM,uBAAuB,EAAE,iBAAiB,CAAC,iBAAiB,CAGxE,CAAC;AAGF,eAAO,MAAM,iBAAiB,EAAE,MAAyC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { calculateModelCost, estimateModelCost, runToolLoop, } from '@plumbus/core';
|
|
2
|
+
export async function legacyConsumer(ai, params) {
|
|
3
|
+
const result = await ai.generateWithUsage({ prompt: 'p', input: {} });
|
|
4
|
+
const cost = result.cost;
|
|
5
|
+
const loop = await runToolLoop(ai, params);
|
|
6
|
+
const total = loop.aggregatedCost;
|
|
7
|
+
return cost + total + calculateModelCost(1, 1, 'local');
|
|
8
|
+
}
|
|
9
|
+
export const legacyEmbeddingCallback = (info) => {
|
|
10
|
+
const numericCost = info.cost;
|
|
11
|
+
void numericCost;
|
|
12
|
+
};
|
|
13
|
+
// @ts-expect-error Unknown-aware estimates cannot be treated as definitely priced.
|
|
14
|
+
export const uncheckedEstimate = estimateModelCost(1, 1, 'local');
|
|
15
|
+
//# sourceMappingURL=legacy-cost-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-cost-api.js","sourceRoot":"","sources":["../../../src/ai/__typecheck-fixtures__/legacy-cost-api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,GAIZ,MAAM,eAAe,CAAC;AAEvB,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,EAAa,EAAE,MAAyB;IAC3E,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IACtE,MAAM,IAAI,GAAW,MAAM,CAAC,IAAI,CAAC;IACjC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAW,IAAI,CAAC,cAAc,CAAC;IAC1C,OAAO,IAAI,GAAG,KAAK,GAAG,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAyC,CAAC,IAAI,EAAE,EAAE;IACpF,MAAM,WAAW,GAAW,IAAI,CAAC,IAAI,CAAC;IACtC,KAAK,WAAW,CAAC;AACnB,CAAC,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,iBAAiB,GAAW,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC"}
|
|
@@ -27,7 +27,7 @@ export interface AICostRecord {
|
|
|
27
27
|
connectionMinutes?: number;
|
|
28
28
|
participantMinutes?: number;
|
|
29
29
|
};
|
|
30
|
-
/**
|
|
30
|
+
/** Provider-reported or catalog-estimated USD cost; null when unknown. */
|
|
31
31
|
cost: number | null;
|
|
32
32
|
latencyMs: number;
|
|
33
33
|
tenantId?: string;
|
|
@@ -55,8 +55,9 @@ export interface BudgetConfig {
|
|
|
55
55
|
* optional so pre-0.3.0 call sites keep compiling; the tracker defaults
|
|
56
56
|
* `status` to `'success'` internally.
|
|
57
57
|
*/
|
|
58
|
-
export type AICostRecordInput = Omit<AICostRecord, 'id' | 'timestamp' | 'status'> & {
|
|
58
|
+
export type AICostRecordInput = Omit<AICostRecord, 'id' | 'timestamp' | 'status' | 'cost'> & {
|
|
59
59
|
status?: AICostRecord['status'];
|
|
60
|
+
cost?: number | null;
|
|
60
61
|
};
|
|
61
62
|
export interface CostTracker {
|
|
62
63
|
record(entry: AICostRecordInput): void;
|
|
@@ -71,7 +72,7 @@ export interface CostTracker {
|
|
|
71
72
|
}): BudgetCheckResult;
|
|
72
73
|
getDailyUsage(tenantId?: string): DailyUsage;
|
|
73
74
|
getRecords(): AICostRecord[];
|
|
74
|
-
/** Fetch
|
|
75
|
+
/** Fetch provider totals for reconciliation; does not infer per-request prices from aggregates. */
|
|
75
76
|
syncCosts?(): Promise<UsageSyncResult>;
|
|
76
77
|
}
|
|
77
78
|
export interface BudgetCheckResult {
|
|
@@ -14,6 +14,10 @@ export interface ModelRate {
|
|
|
14
14
|
inputPerMTok: number;
|
|
15
15
|
/** USD per 1M output tokens (0 for embedding / moderation / free models) */
|
|
16
16
|
outputPerMTok: number;
|
|
17
|
+
/** Published cache-read USD per MTok; defaults to 0.1× input when omitted. */
|
|
18
|
+
cachedInputPerMTok?: number;
|
|
19
|
+
/** Above this inclusive input-token boundary, charge 2× input/cache and 1.5× output. */
|
|
20
|
+
longContextThreshold?: number;
|
|
17
21
|
/**
|
|
18
22
|
* Capability classification. Optional on the type for backward compat —
|
|
19
23
|
* existing consumers that destructure only the rate fields keep working —
|
|
@@ -34,18 +38,23 @@ export declare function findModelRate(model: string): ModelRate | null;
|
|
|
34
38
|
* catalog. Order is not guaranteed.
|
|
35
39
|
*/
|
|
36
40
|
export declare function allKnownModels(): ReadonlyArray<readonly [string, ModelRate]>;
|
|
41
|
+
/** Legacy numeric cost helper. Use estimateModelCost when unknown must differ from free. */
|
|
42
|
+
export declare function calculateModelCost(inputTokens: number, outputTokens: number, model: string, options?: {
|
|
43
|
+
cachedInputTokens?: number;
|
|
44
|
+
cacheWriteTokens?: number;
|
|
45
|
+
}): number;
|
|
37
46
|
/**
|
|
38
47
|
* Calculate the USD cost for a single AI request based on published per-token rates.
|
|
39
48
|
*
|
|
40
49
|
* Pricing adjustments applied:
|
|
41
|
-
* - **Cached input tokens**:
|
|
50
|
+
* - **Cached input tokens**: published per-model rate, defaulting to 0.1× input.
|
|
42
51
|
* - **Cache write tokens**: charged at 1.25× the base input rate (Anthropic 5-min cache).
|
|
43
52
|
* - **Long context premium**: for Claude Sonnet 4 / 4.5, when total input exceeds
|
|
44
|
-
* 200K tokens
|
|
53
|
+
* 200K tokens, or GPT-5.6 Sol over 272K, charge 2× input/cache and 1.5× output.
|
|
45
54
|
*
|
|
46
|
-
* Returns
|
|
55
|
+
* Returns undefined for unknown/unsupported models; explicitly free adapters may report zero.
|
|
47
56
|
*/
|
|
48
|
-
export declare function
|
|
57
|
+
export declare function estimateModelCost(inputTokens: number, outputTokens: number, model: string, options?: {
|
|
49
58
|
cachedInputTokens?: number;
|
|
50
59
|
cacheWriteTokens?: number;
|
|
51
|
-
}): number;
|
|
60
|
+
}): number | undefined;
|
|
@@ -22,6 +22,8 @@ export interface UsageClientConfig {
|
|
|
22
22
|
apiKey: string;
|
|
23
23
|
baseUrl?: string;
|
|
24
24
|
organizationId?: string;
|
|
25
|
+
/** Billing endpoint timeout in milliseconds (default 10000). */
|
|
26
|
+
timeoutMs?: number;
|
|
25
27
|
}
|
|
26
28
|
export declare function createUsageAPIClient(config: UsageClientConfig): UsageAPIClient;
|
|
27
29
|
export declare class UsageAPIError extends Error {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-service.d.ts","sourceRoot":"","sources":["../../src/ai/ai-service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,aAAa,EAIb,SAAS,EAIV,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,iBAAiB,EAAoB,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/F,OAAO,KAAK,EAAE,YAAY,EAAqB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACtF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-service.d.ts","sourceRoot":"","sources":["../../src/ai/ai-service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,aAAa,EAIb,SAAS,EAIV,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,iBAAiB,EAAoB,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/F,OAAO,KAAK,EAAE,YAAY,EAAqB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACtF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EACL,KAAK,iBAAiB,EAUvB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AAIzB,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,MAAM,EAAE,YAAY,EACpB,WAAW,CAAC,EAAE,aAAa,KACxB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAG1B,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC7C,8DAA8D;IAC9D,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,cAAc,CAAC,EAAE,uBAAuB,CAAC;IACzC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC,0DAA0D,CAAC;IACjF,gFAAgF;IAChF,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CACtB,MAAM,EACN;QACE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,iBAAiB,CAAC;QAC9B,mCAAmC;QACnC,eAAe,CAAC,EAAE,eAAe,CAAC;KACnC,CACF,CAAC,kCAAkC,CAAC;IACrC,MAAM,CAAC,EAAE;QACP,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,GAAG,iBAAiB,CAAC,GAC5D,eAAe,CAMjB;AAUD,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CA8zClE"}
|
package/dist/ai/ai-service.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// Integrates: provider adapter, prompt registry, validation, cost tracking, security, RAG, explainability
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { AIBudgetExceededError, AISecurityBlockedError } from '../errors/data-errors.js';
|
|
6
|
-
import {
|
|
6
|
+
import { normalizeCost, validateTokenUsage } from './usage-validation.js';
|
|
7
|
+
import { estimateModelCost as calculateModelCost, findModelRate } from './model-pricing.js';
|
|
7
8
|
import { normalizeFinishReason, } from './provider.js';
|
|
8
9
|
import { AIIncompleteOutputError, AIRefusalError } from './refusal.js';
|
|
9
10
|
import { checkPromptSecurity } from './security.js';
|
|
@@ -32,7 +33,36 @@ export function createAIService(config) {
|
|
|
32
33
|
const available = Object.keys(providers).join(', ');
|
|
33
34
|
throw new Error(`AI provider "${name}" not configured. Available: ${available}`);
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
+
// Every provider attempt, including validation retries, checks the budget.
|
|
37
|
+
const checkRequest = (request) => {
|
|
38
|
+
const input = JSON.stringify({
|
|
39
|
+
system: request.system,
|
|
40
|
+
prompt: request.prompt,
|
|
41
|
+
messages: request.messages,
|
|
42
|
+
tools: request.tools,
|
|
43
|
+
responseSchema: request.responseSchema,
|
|
44
|
+
});
|
|
45
|
+
checkBudget(Math.ceil(Buffer.byteLength(input, 'utf8') / 4) + (request.maxTokens ?? 0));
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
...adapter,
|
|
49
|
+
name: adapter.name,
|
|
50
|
+
embed: (request) => adapter.embed(request),
|
|
51
|
+
async complete(request) {
|
|
52
|
+
checkRequest(request);
|
|
53
|
+
const response = await adapter.complete(request);
|
|
54
|
+
validateTokenUsage(response.usage);
|
|
55
|
+
return { ...response, cost: normalizeCost(response.cost) ?? undefined };
|
|
56
|
+
},
|
|
57
|
+
async *stream(request) {
|
|
58
|
+
checkRequest(request);
|
|
59
|
+
for await (const event of adapter.stream(request)) {
|
|
60
|
+
if (event.usage)
|
|
61
|
+
validateTokenUsage(event.usage);
|
|
62
|
+
yield { ...event, cost: normalizeCost(event.cost) ?? undefined };
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
};
|
|
36
66
|
}
|
|
37
67
|
function getSingleTextFieldName(schema) {
|
|
38
68
|
const shape = schema instanceof z.ZodObject ? schema.shape : undefined;
|
|
@@ -94,12 +124,25 @@ export function createAIService(config) {
|
|
|
94
124
|
* so the hook gets a fully-formed `AICostRecord`.
|
|
95
125
|
*/
|
|
96
126
|
async function recordProviderCost(entry, costContext) {
|
|
127
|
+
entry = {
|
|
128
|
+
...entry,
|
|
129
|
+
tenantId: config.budget ? config.budget.tenantId : entry.tenantId,
|
|
130
|
+
actor: config.budget ? config.budget.actor : entry.actor,
|
|
131
|
+
cost: (entry.cost == null ? undefined : normalizeCost(entry.cost)) ??
|
|
132
|
+
(entry.cost == null && entry.usage.totalTokens > 0 && findModelRate(entry.model)
|
|
133
|
+
? calculateModelCost(entry.usage.inputTokens, entry.usage.outputTokens, entry.model, {
|
|
134
|
+
cachedInputTokens: entry.usage.cachedInputTokens,
|
|
135
|
+
cacheWriteTokens: entry.usage.cacheWriteTokens,
|
|
136
|
+
})
|
|
137
|
+
: null),
|
|
138
|
+
};
|
|
97
139
|
if (costTracker) {
|
|
98
140
|
costTracker.record(entry);
|
|
99
141
|
}
|
|
100
142
|
if (onAICostRecorded) {
|
|
101
143
|
const finalEntry = {
|
|
102
144
|
...entry,
|
|
145
|
+
cost: normalizeCost(entry.cost),
|
|
103
146
|
status: entry.status ?? 'success',
|
|
104
147
|
id: crypto.randomUUID(),
|
|
105
148
|
timestamp: new Date(),
|
|
@@ -147,15 +190,12 @@ export function createAIService(config) {
|
|
|
147
190
|
// Simple template resolution: replace top-level {{key}} placeholders.
|
|
148
191
|
const substitutedKeys = new Set();
|
|
149
192
|
const renderTemplate = (template) => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return text;
|
|
193
|
+
return template.replace(/\{\{([^{}]+)\}\}/g, (placeholder, key) => {
|
|
194
|
+
if (!Object.hasOwn(input, key))
|
|
195
|
+
return placeholder;
|
|
196
|
+
substitutedKeys.add(key);
|
|
197
|
+
return String(input[key]);
|
|
198
|
+
});
|
|
159
199
|
};
|
|
160
200
|
const system = def.system ? renderTemplate(def.system) : undefined;
|
|
161
201
|
const text = renderTemplate(def.description ?? promptName);
|
|
@@ -356,6 +396,11 @@ export function createAIService(config) {
|
|
|
356
396
|
throw err;
|
|
357
397
|
}
|
|
358
398
|
const latencyMs = performance.now() - start;
|
|
399
|
+
const cost = providerCost ??
|
|
400
|
+
calculateModelCost(totalUsage.inputTokens, totalUsage.outputTokens, resolvedModel, {
|
|
401
|
+
cachedInputTokens: totalUsage.cachedInputTokens,
|
|
402
|
+
cacheWriteTokens: totalUsage.cacheWriteTokens,
|
|
403
|
+
});
|
|
359
404
|
// Track cost (success path)
|
|
360
405
|
await recordProviderCost({
|
|
361
406
|
model: resolvedModel,
|
|
@@ -363,7 +408,7 @@ export function createAIService(config) {
|
|
|
363
408
|
promptName: hasPromptDef ? params.prompt : undefined,
|
|
364
409
|
operation: 'generate',
|
|
365
410
|
usage: totalUsage,
|
|
366
|
-
cost: null,
|
|
411
|
+
cost: cost ?? null,
|
|
367
412
|
latencyMs,
|
|
368
413
|
tenantId: config.budget?.tenantId,
|
|
369
414
|
actor: config.budget?.actor,
|
|
@@ -376,7 +421,7 @@ export function createAIService(config) {
|
|
|
376
421
|
promptName: hasPromptDef ? params.prompt : undefined,
|
|
377
422
|
model: promptInfo.model ?? config.defaultModel,
|
|
378
423
|
provider: activeProvider.name,
|
|
379
|
-
input:
|
|
424
|
+
input: inputForAI,
|
|
380
425
|
output: result,
|
|
381
426
|
usage: totalUsage,
|
|
382
427
|
validation: {
|
|
@@ -389,11 +434,6 @@ export function createAIService(config) {
|
|
|
389
434
|
latencyMs,
|
|
390
435
|
});
|
|
391
436
|
}
|
|
392
|
-
const cost = providerCost ??
|
|
393
|
-
calculateModelCost(totalUsage.inputTokens, totalUsage.outputTokens, resolvedModel, {
|
|
394
|
-
cachedInputTokens: totalUsage.cachedInputTokens,
|
|
395
|
-
cacheWriteTokens: totalUsage.cacheWriteTokens,
|
|
396
|
-
});
|
|
397
437
|
if (toolsEnabled &&
|
|
398
438
|
toolFinishNormalized === 'tool_calls' &&
|
|
399
439
|
toolCallsResult &&
|
|
@@ -405,7 +445,8 @@ export function createAIService(config) {
|
|
|
405
445
|
usage: totalUsage,
|
|
406
446
|
model: resolvedModel,
|
|
407
447
|
provider: activeProvider.name,
|
|
408
|
-
cost,
|
|
448
|
+
cost: cost ?? 0,
|
|
449
|
+
costAvailable: cost != null,
|
|
409
450
|
};
|
|
410
451
|
}
|
|
411
452
|
let resolvedFinishReason = finalFinishReason;
|
|
@@ -418,13 +459,17 @@ export function createAIService(config) {
|
|
|
418
459
|
usage: totalUsage,
|
|
419
460
|
model: resolvedModel,
|
|
420
461
|
provider: activeProvider.name,
|
|
421
|
-
cost,
|
|
462
|
+
cost: cost ?? 0,
|
|
463
|
+
costAvailable: cost != null,
|
|
422
464
|
};
|
|
423
465
|
}
|
|
424
466
|
function generateWithUsage(config) {
|
|
425
467
|
return _generateCore(config);
|
|
426
468
|
}
|
|
427
469
|
return {
|
|
470
|
+
withContext(identity) {
|
|
471
|
+
return createAIService({ ...config, budget: { ...identity } });
|
|
472
|
+
},
|
|
428
473
|
features: { perCallProviderModelReasoning: true },
|
|
429
474
|
recordProviderCost,
|
|
430
475
|
checkProviderCostBudget(config = {}) {
|
|
@@ -604,17 +649,18 @@ export function createAIService(config) {
|
|
|
604
649
|
}
|
|
605
650
|
// Compute cost and model info for the done event
|
|
606
651
|
const streamCost = streamProviderCost ??
|
|
607
|
-
(streamUsage
|
|
652
|
+
(streamUsage && findModelRate(resolvedModel)
|
|
608
653
|
? calculateModelCost(streamUsage.inputTokens, streamUsage.outputTokens, resolvedModel, {
|
|
609
654
|
cachedInputTokens: streamUsage.cachedInputTokens,
|
|
610
655
|
cacheWriteTokens: streamUsage.cacheWriteTokens,
|
|
611
656
|
})
|
|
612
|
-
:
|
|
657
|
+
: undefined);
|
|
613
658
|
const doneBase = {
|
|
614
659
|
usage: streamUsage,
|
|
615
660
|
model: resolvedModel,
|
|
616
661
|
provider: activeProvider.name,
|
|
617
662
|
cost: streamCost,
|
|
663
|
+
costAvailable: streamCost != null,
|
|
618
664
|
finishReason: providerFinishReason,
|
|
619
665
|
};
|
|
620
666
|
if (request.responseSchema &&
|
|
@@ -626,7 +672,7 @@ export function createAIService(config) {
|
|
|
626
672
|
promptName: hasPromptDef ? params.prompt : undefined,
|
|
627
673
|
operation: 'generate',
|
|
628
674
|
usage: failureUsage,
|
|
629
|
-
cost: null,
|
|
675
|
+
cost: streamCost ?? null,
|
|
630
676
|
latencyMs: 0,
|
|
631
677
|
tenantId: config.budget?.tenantId,
|
|
632
678
|
actor: config.budget?.actor,
|
|
@@ -650,7 +696,7 @@ export function createAIService(config) {
|
|
|
650
696
|
promptName: hasPromptDef ? params.prompt : undefined,
|
|
651
697
|
operation: 'generate',
|
|
652
698
|
usage: streamUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
653
|
-
cost: null,
|
|
699
|
+
cost: streamCost ?? null,
|
|
654
700
|
latencyMs: performance.now() - streamStart,
|
|
655
701
|
tenantId: config.budget?.tenantId,
|
|
656
702
|
actor: config.budget?.actor,
|
|
@@ -662,7 +708,7 @@ export function createAIService(config) {
|
|
|
662
708
|
promptName: hasPromptDef ? params.prompt : undefined,
|
|
663
709
|
model: resolvedModel,
|
|
664
710
|
provider: activeProvider.name,
|
|
665
|
-
input:
|
|
711
|
+
input: inputForAI,
|
|
666
712
|
output: singleTextField
|
|
667
713
|
? { [singleTextField]: fullText.trim() }
|
|
668
714
|
: { text: fullText.trim() },
|
|
@@ -686,6 +732,7 @@ export function createAIService(config) {
|
|
|
686
732
|
let validatedData;
|
|
687
733
|
let validationFellBackToNonStreaming = false;
|
|
688
734
|
let fallbackUsage;
|
|
735
|
+
let fallbackCost;
|
|
689
736
|
try {
|
|
690
737
|
const parsed = JSON.parse(fullText);
|
|
691
738
|
validatedData = promptDef.output.parse(parsed);
|
|
@@ -731,6 +778,9 @@ export function createAIService(config) {
|
|
|
731
778
|
const validated = await generateWithValidation(activeProvider, { ...request, responseFormat: 'json' }, promptDef.output, { ...config.validation, model: resolvedModel, provider: activeProvider.name });
|
|
732
779
|
validatedData = validated.data;
|
|
733
780
|
fallbackUsage = validated.usage;
|
|
781
|
+
fallbackCost =
|
|
782
|
+
validated.cost ??
|
|
783
|
+
calculateModelCost(validated.usage.inputTokens, validated.usage.outputTokens, resolvedModel, validated.usage);
|
|
734
784
|
}
|
|
735
785
|
catch (fallbackErr) {
|
|
736
786
|
// Bill the caller for the combined streamUsage + whatever the
|
|
@@ -769,6 +819,11 @@ export function createAIService(config) {
|
|
|
769
819
|
const successUsage = validationFellBackToNonStreaming
|
|
770
820
|
? sumUsage(streamUsage, fallbackUsage)
|
|
771
821
|
: (streamUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 });
|
|
822
|
+
const successCost = validationFellBackToNonStreaming
|
|
823
|
+
? streamCost != null && fallbackCost != null
|
|
824
|
+
? streamCost + fallbackCost
|
|
825
|
+
: undefined
|
|
826
|
+
: streamCost;
|
|
772
827
|
await recordProviderCost({
|
|
773
828
|
model: resolvedModel,
|
|
774
829
|
provider: activeProvider.name,
|
|
@@ -776,7 +831,7 @@ export function createAIService(config) {
|
|
|
776
831
|
operation: 'generate',
|
|
777
832
|
fallbackUsed: validationFellBackToNonStreaming || undefined,
|
|
778
833
|
usage: successUsage,
|
|
779
|
-
cost: null,
|
|
834
|
+
cost: successCost ?? null,
|
|
780
835
|
latencyMs: 0,
|
|
781
836
|
tenantId: config.budget?.tenantId,
|
|
782
837
|
actor: config.budget?.actor,
|
|
@@ -786,6 +841,9 @@ export function createAIService(config) {
|
|
|
786
841
|
type: 'done',
|
|
787
842
|
data: validatedData,
|
|
788
843
|
...doneBase,
|
|
844
|
+
usage: successUsage,
|
|
845
|
+
cost: successCost,
|
|
846
|
+
costAvailable: successCost != null,
|
|
789
847
|
validationFallbackFired: validationFellBackToNonStreaming,
|
|
790
848
|
};
|
|
791
849
|
}
|
|
@@ -796,7 +854,7 @@ export function createAIService(config) {
|
|
|
796
854
|
promptName: hasPromptDef ? params.prompt : undefined,
|
|
797
855
|
operation: 'generate',
|
|
798
856
|
usage: streamUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
799
|
-
cost: null,
|
|
857
|
+
cost: streamCost ?? null,
|
|
800
858
|
latencyMs: 0,
|
|
801
859
|
tenantId: config.budget?.tenantId,
|
|
802
860
|
actor: config.budget?.actor,
|
|
@@ -807,6 +865,8 @@ export function createAIService(config) {
|
|
|
807
865
|
},
|
|
808
866
|
async extract(params) {
|
|
809
867
|
const start = performance.now();
|
|
868
|
+
const { inputForAI, securityResult } = applyPromptSecurity({ text: params.text });
|
|
869
|
+
const textForAI = String(inputForAI.text);
|
|
810
870
|
checkBudget();
|
|
811
871
|
// extract uses default provider (no prompt-level routing)
|
|
812
872
|
const activeProvider = resolveProvider();
|
|
@@ -814,7 +874,7 @@ export function createAIService(config) {
|
|
|
814
874
|
const systemPrompt = 'Extract structured data from the following text. Return valid JSON matching the required schema.';
|
|
815
875
|
const request = {
|
|
816
876
|
system: systemPrompt,
|
|
817
|
-
prompt:
|
|
877
|
+
prompt: textForAI,
|
|
818
878
|
model: config.defaultModel,
|
|
819
879
|
responseFormat: 'json',
|
|
820
880
|
responseSchema: config.enableStrictStructuredOutputs
|
|
@@ -855,7 +915,9 @@ export function createAIService(config) {
|
|
|
855
915
|
provider: activeProvider.name,
|
|
856
916
|
operation: 'extract',
|
|
857
917
|
usage: validated.usage,
|
|
858
|
-
cost:
|
|
918
|
+
cost: validated.cost ??
|
|
919
|
+
calculateModelCost(validated.usage.inputTokens, validated.usage.outputTokens, resolvedModel, validated.usage) ??
|
|
920
|
+
null,
|
|
859
921
|
latencyMs,
|
|
860
922
|
tenantId: config.budget?.tenantId,
|
|
861
923
|
actor: config.budget?.actor,
|
|
@@ -866,7 +928,8 @@ export function createAIService(config) {
|
|
|
866
928
|
operation: 'extract',
|
|
867
929
|
model: config.defaultModel,
|
|
868
930
|
provider: activeProvider.name,
|
|
869
|
-
input:
|
|
931
|
+
input: inputForAI,
|
|
932
|
+
securityWarnings: securityResult?.warnings.map((warning) => warning.message),
|
|
870
933
|
output: validated.data,
|
|
871
934
|
usage: validated.usage,
|
|
872
935
|
validation: { passed: validated.attempts === 1, attempts: validated.attempts },
|
|
@@ -879,6 +942,8 @@ export function createAIService(config) {
|
|
|
879
942
|
},
|
|
880
943
|
async classify(params) {
|
|
881
944
|
const start = performance.now();
|
|
945
|
+
const { inputForAI, securityResult } = applyPromptSecurity({ text: params.text });
|
|
946
|
+
const textForAI = String(inputForAI.text);
|
|
882
947
|
checkBudget();
|
|
883
948
|
// classify uses default provider (no prompt-level routing)
|
|
884
949
|
const activeProvider = resolveProvider();
|
|
@@ -886,7 +951,7 @@ export function createAIService(config) {
|
|
|
886
951
|
const systemPrompt = 'Classify the following text into one or more of the provided labels. Return a JSON array of matching label strings.';
|
|
887
952
|
const request = {
|
|
888
953
|
system: systemPrompt,
|
|
889
|
-
prompt: `Labels: ${JSON.stringify(params.labels)}\n\nText: ${
|
|
954
|
+
prompt: `Labels: ${JSON.stringify(params.labels)}\n\nText: ${textForAI}`,
|
|
890
955
|
model: config.defaultModel,
|
|
891
956
|
responseFormat: 'json',
|
|
892
957
|
responseSchema: config.enableStrictStructuredOutputs
|
|
@@ -930,7 +995,9 @@ export function createAIService(config) {
|
|
|
930
995
|
provider: activeProvider.name,
|
|
931
996
|
operation: 'classify',
|
|
932
997
|
usage: validated.usage,
|
|
933
|
-
cost:
|
|
998
|
+
cost: validated.cost ??
|
|
999
|
+
calculateModelCost(validated.usage.inputTokens, validated.usage.outputTokens, resolvedModel, validated.usage) ??
|
|
1000
|
+
null,
|
|
934
1001
|
latencyMs,
|
|
935
1002
|
tenantId: config.budget?.tenantId,
|
|
936
1003
|
actor: config.budget?.actor,
|
|
@@ -941,7 +1008,8 @@ export function createAIService(config) {
|
|
|
941
1008
|
operation: 'classify',
|
|
942
1009
|
model: config.defaultModel,
|
|
943
1010
|
provider: activeProvider.name,
|
|
944
|
-
input: { labels: params.labels,
|
|
1011
|
+
input: { labels: params.labels, ...inputForAI },
|
|
1012
|
+
securityWarnings: securityResult?.warnings.map((warning) => warning.message),
|
|
945
1013
|
output: result,
|
|
946
1014
|
usage: validated.usage,
|
|
947
1015
|
validation: { passed: validated.attempts === 1, attempts: validated.attempts },
|