@chatcops/core 0.4.0 → 0.5.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.
- package/dist/agent/chat.d.ts +13 -0
- package/dist/agent/chat.d.ts.map +1 -0
- package/dist/agent/chat.js +42 -0
- package/dist/agent/chat.js.map +1 -0
- package/dist/agent/errors.d.ts +14 -0
- package/dist/agent/errors.d.ts.map +1 -0
- package/dist/agent/errors.js +24 -0
- package/dist/agent/errors.js.map +1 -0
- package/dist/agent/index.d.ts +5 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +5 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/run-turn.d.ts +68 -0
- package/dist/agent/run-turn.d.ts.map +1 -0
- package/dist/agent/run-turn.js +344 -0
- package/dist/agent/run-turn.js.map +1 -0
- package/dist/agent/tool-registry.d.ts +28 -0
- package/dist/agent/tool-registry.d.ts.map +1 -0
- package/dist/agent/tool-registry.js +69 -0
- package/dist/agent/tool-registry.js.map +1 -0
- package/dist/analytics/index.d.ts +1 -0
- package/dist/analytics/index.d.ts.map +1 -1
- package/dist/analytics/index.js +1 -0
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/sink.d.ts +24 -0
- package/dist/analytics/sink.d.ts.map +1 -0
- package/dist/analytics/sink.js +106 -0
- package/dist/analytics/sink.js.map +1 -0
- package/dist/conversation/index.d.ts +1 -0
- package/dist/conversation/index.d.ts.map +1 -1
- package/dist/conversation/index.js.map +1 -1
- package/dist/conversation/manager.d.ts +62 -6
- package/dist/conversation/manager.d.ts.map +1 -1
- package/dist/conversation/manager.js +109 -34
- package/dist/conversation/manager.js.map +1 -1
- package/dist/conversation/store.d.ts +6 -5
- package/dist/conversation/store.d.ts.map +1 -1
- package/dist/conversation/store.js +4 -0
- package/dist/conversation/store.js.map +1 -1
- package/dist/index.d.ts +18 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/dist/limits/budget.d.ts +84 -0
- package/dist/limits/budget.d.ts.map +1 -0
- package/dist/limits/budget.js +114 -0
- package/dist/limits/budget.js.map +1 -0
- package/dist/limits/rate-limit.d.ts +53 -0
- package/dist/limits/rate-limit.d.ts.map +1 -0
- package/dist/limits/rate-limit.js +68 -0
- package/dist/limits/rate-limit.js.map +1 -0
- package/dist/parts.d.ts +9 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +17 -0
- package/dist/parts.js.map +1 -0
- package/dist/protocol/ai-sdk.d.ts +27 -0
- package/dist/protocol/ai-sdk.d.ts.map +1 -0
- package/dist/protocol/ai-sdk.js +127 -0
- package/dist/protocol/ai-sdk.js.map +1 -0
- package/dist/protocol/errors.d.ts +10 -0
- package/dist/protocol/errors.d.ts.map +1 -0
- package/dist/protocol/errors.js +42 -0
- package/dist/protocol/errors.js.map +1 -0
- package/dist/protocol/index.d.ts +9 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +6 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/legacy.d.ts +18 -0
- package/dist/protocol/legacy.d.ts.map +1 -0
- package/dist/protocol/legacy.js +41 -0
- package/dist/protocol/legacy.js.map +1 -0
- package/dist/protocol/sse.d.ts +10 -0
- package/dist/protocol/sse.d.ts.map +1 -0
- package/dist/protocol/sse.js +15 -0
- package/dist/protocol/sse.js.map +1 -0
- package/dist/protocol/v1.d.ts +140 -0
- package/dist/protocol/v1.d.ts.map +1 -0
- package/dist/protocol/v1.js +15 -0
- package/dist/protocol/v1.js.map +1 -0
- package/dist/providers/base.d.ts +11 -27
- package/dist/providers/base.d.ts.map +1 -1
- package/dist/providers/base.js +34 -45
- package/dist/providers/base.js.map +1 -1
- package/dist/providers/claude.d.ts +26 -5
- package/dist/providers/claude.d.ts.map +1 -1
- package/dist/providers/claude.js +176 -89
- package/dist/providers/claude.js.map +1 -1
- package/dist/providers/gemini.d.ts +18 -3
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +208 -91
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/history.d.ts +52 -0
- package/dist/providers/history.d.ts.map +1 -0
- package/dist/providers/history.js +212 -0
- package/dist/providers/history.js.map +1 -0
- package/dist/providers/index.d.ts +5 -5
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +5 -4
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai.d.ts +27 -6
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +209 -112
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/registry.d.ts +7 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +12 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/schema.d.ts +16 -0
- package/dist/providers/schema.d.ts.map +1 -0
- package/dist/providers/schema.js +80 -0
- package/dist/providers/schema.js.map +1 -0
- package/dist/providers/tool-execution.d.ts +10 -0
- package/dist/providers/tool-execution.d.ts.map +1 -1
- package/dist/providers/tool-execution.js +38 -12
- package/dist/providers/tool-execution.js.map +1 -1
- package/dist/providers/types.d.ts +71 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/storage/adapt.d.ts +15 -0
- package/dist/storage/adapt.d.ts.map +1 -0
- package/dist/storage/adapt.js +83 -0
- package/dist/storage/adapt.js.map +1 -0
- package/dist/storage/errors.d.ts +18 -0
- package/dist/storage/errors.d.ts.map +1 -0
- package/dist/storage/errors.js +28 -0
- package/dist/storage/errors.js.map +1 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +4 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/memory.d.ts +24 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +265 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/types.d.ts +118 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/testing/conformance.d.ts +29 -0
- package/dist/testing/conformance.d.ts.map +1 -0
- package/dist/testing/conformance.js +335 -0
- package/dist/testing/conformance.js.map +1 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/provider.d.ts +45 -0
- package/dist/testing/provider.d.ts.map +1 -0
- package/dist/testing/provider.js +82 -0
- package/dist/testing/provider.js.map +1 -0
- package/dist/tools/base.d.ts +14 -4
- package/dist/tools/base.d.ts.map +1 -1
- package/dist/tools/base.js +4 -2
- package/dist/tools/base.js.map +1 -1
- package/dist/tools/lead-capture.d.ts +3 -1
- package/dist/tools/lead-capture.d.ts.map +1 -1
- package/dist/tools/lead-capture.js +4 -0
- package/dist/tools/lead-capture.js.map +1 -1
- package/dist/types.d.ts +139 -7
- package/dist/types.d.ts.map +1 -1
- package/package.json +50 -2
package/dist/index.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { partsOf, messageText } from './parts.js';
|
|
2
|
+
// Wire protocol
|
|
3
|
+
export { PROTOCOL_VERSION, encodeSSE, SSE_PING, SSE_DONE, SSE_HEADERS, statusForError, errorBody, describeInvalidRequest, toLegacyChunk, aiSdkEncoder, fromAiSdkRequest, AI_SDK_STREAM_HEADER } from './protocol/index.js';
|
|
4
|
+
export { createProvider, isAIProvider, normalizeHistory, normalizeTurns, trimHistory, estimateTokens } from './providers/index.js';
|
|
5
|
+
export { registerProvider } from './providers/registry.js';
|
|
6
|
+
export { toolInputSchema, legacyParametersToSchema, validateTools, ToolDefinitionError } from './providers/index.js';
|
|
7
|
+
// Turn engine
|
|
8
|
+
export { runTurn, DEFAULT_MAX_STEPS, toolRegistry, executorRegistry, toolOutput, MAX_TOOL_OUTPUT_CHARS, RefusalError, ContentFilterError, MaxStepsError, } from './agent/index.js';
|
|
2
9
|
export { toolToDefinition, LeadCaptureTool } from './tools/index.js';
|
|
3
10
|
export { TextKnowledgeSource, FAQKnowledgeSource } from './knowledge/index.js';
|
|
4
11
|
export { ConversationManager, MemoryStore } from './conversation/index.js';
|
|
12
|
+
export { ConflictError, isConflictError, CONFLICT_ERROR_CODE, memoryStorage, withAppend, isLegacyConversationStore, } from './storage/index.js';
|
|
13
|
+
// Budgets
|
|
14
|
+
export { createBudgetGuard, BudgetExceededError, DEFAULT_BUDGETS, withDefaultBudgets } from './limits/budget.js';
|
|
15
|
+
// Rate limiting
|
|
16
|
+
export { createRateLimiter } from './limits/rate-limit.js';
|
|
5
17
|
// Analytics
|
|
6
|
-
export { AnalyticsCollector } from './analytics/index.js';
|
|
18
|
+
export { AnalyticsCollector, BufferedEventSink, statsFromSink } from './analytics/index.js';
|
|
7
19
|
// i18n
|
|
8
20
|
export { getLocale, getAvailableLocales } from './i18n/index.js';
|
|
9
21
|
export { en, es, hi, fr, de, ja, zh, ar } from './i18n/index.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElD,gBAAgB;AAChB,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,sBAAsB,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAwB3N,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACnI,OAAO,EAAE,gBAAgB,EAAwB,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAErH,cAAc;AACd,OAAO,EACL,OAAO,EACP,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,qBAAqB,EACrB,YAAY,EACZ,kBAAkB,EAClB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAK1B,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAIrE,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAI/E,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAkB3E,OAAO,EACL,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC;AAE5B,UAAU;AACV,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGjH,gBAAgB;AAChB,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG3D,YAAY;AACZ,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG5F,OAAO;AACP,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CounterStore } from '../storage/types.js';
|
|
2
|
+
import type { TokenUsage } from '../types.js';
|
|
3
|
+
/** How much a scope may use. Unset limits don't apply. */
|
|
4
|
+
export interface BudgetLimit {
|
|
5
|
+
/** Input plus output tokens, counted at face value (cached input included). */
|
|
6
|
+
tokens?: number;
|
|
7
|
+
/** Replies. */
|
|
8
|
+
messages?: number;
|
|
9
|
+
}
|
|
10
|
+
/** `false` turns a scope off, including its default. */
|
|
11
|
+
export interface Budgets {
|
|
12
|
+
/** Over a conversation's lifetime (counters kept 30 days). */
|
|
13
|
+
perConversation?: BudgetLimit | false;
|
|
14
|
+
/** Per visitor IP, per UTC day. Skipped for requests without a known IP. */
|
|
15
|
+
perIpPerDay?: BudgetLimit | false;
|
|
16
|
+
/** Everything together, per UTC day: the backstop for a bill you didn't expect. */
|
|
17
|
+
globalPerDay?: (BudgetLimit & {
|
|
18
|
+
/** Dollars, priced with `price`. */
|
|
19
|
+
usd?: number;
|
|
20
|
+
}) | false;
|
|
21
|
+
/**
|
|
22
|
+
* What a step cost in dollars, for `globalPerDay.usd`. Price cached input
|
|
23
|
+
* at its discount here; token budgets count it in full.
|
|
24
|
+
*/
|
|
25
|
+
price?: (usage: TokenUsage) => number;
|
|
26
|
+
}
|
|
27
|
+
/** Who a reply is for. */
|
|
28
|
+
export interface BudgetContext {
|
|
29
|
+
conversationId: string;
|
|
30
|
+
ip?: string;
|
|
31
|
+
}
|
|
32
|
+
export type BudgetScope = 'conversation' | 'ip' | 'global';
|
|
33
|
+
/** A budget is used up. The reply ends with `budget_exceeded`. */
|
|
34
|
+
export declare class BudgetExceededError extends Error {
|
|
35
|
+
readonly scope: BudgetScope;
|
|
36
|
+
readonly code = "budget_exceeded";
|
|
37
|
+
constructor(scope: BudgetScope);
|
|
38
|
+
}
|
|
39
|
+
export interface BudgetGuard {
|
|
40
|
+
/**
|
|
41
|
+
* Checks the budgets before a model request. Throws `BudgetExceededError`
|
|
42
|
+
* when one is used up. Resolves the most tokens left in any token budget,
|
|
43
|
+
* so the caller can cap the reply length.
|
|
44
|
+
*/
|
|
45
|
+
before(context: BudgetContext, step: number): Promise<{
|
|
46
|
+
tokensLeft?: number;
|
|
47
|
+
}>;
|
|
48
|
+
/** Charges a model request's usage. */
|
|
49
|
+
after(context: BudgetContext, usage: TokenUsage): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* What applies when a scope isn't set. Replies, not tokens, per conversation:
|
|
53
|
+
* the widget keeps a conversation across visits, and a token count there
|
|
54
|
+
* would grow with the size of your system prompt. Tokens per IP per day are
|
|
55
|
+
* the cost backstop, since a script can start new conversations at will.
|
|
56
|
+
* There's no global default, because no number fits every site.
|
|
57
|
+
*/
|
|
58
|
+
export declare const DEFAULT_BUDGETS: {
|
|
59
|
+
readonly perConversation: {
|
|
60
|
+
readonly messages: 200;
|
|
61
|
+
};
|
|
62
|
+
readonly perIpPerDay: {
|
|
63
|
+
readonly tokens: 2000000;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Fills the scopes you didn't set with `DEFAULT_BUDGETS`. A scope you set
|
|
68
|
+
* replaces its default whole; `false` turns it off. `false` for the whole
|
|
69
|
+
* option turns budgets off and resolves `undefined`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function withDefaultBudgets(budgets?: Budgets | false): {
|
|
72
|
+
budgets: Budgets;
|
|
73
|
+
defaulted: BudgetScope[];
|
|
74
|
+
} | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Token, reply and dollar budgets on a `CounterStore`, so they hold across
|
|
77
|
+
* server instances. Checked before each model request and charged after it,
|
|
78
|
+
* so a reply can go over by at most one request.
|
|
79
|
+
*/
|
|
80
|
+
export declare function createBudgetGuard(counters: CounterStore, budgets: Budgets, options?: {
|
|
81
|
+
prefix?: string;
|
|
82
|
+
now?: () => number;
|
|
83
|
+
}): BudgetGuard;
|
|
84
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../src/limits/budget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,0DAA0D;AAC1D,MAAM,WAAW,WAAW;IAC1B,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wDAAwD;AACxD,MAAM,WAAW,OAAO;IACtB,8DAA8D;IAC9D,eAAe,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IACtC,4EAA4E;IAC5E,WAAW,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAClC,mFAAmF;IACnF,YAAY,CAAC,EACT,CAAC,WAAW,GAAG;QACb,oCAAoC;QACpC,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC,GACF,KAAK,CAAC;IACV;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC;CACvC;AAED,0BAA0B;AAC1B,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,IAAI,GAAG,QAAQ,CAAC;AAE3D,kEAAkE;AAClE,qBAAa,mBAAoB,SAAQ,KAAK;IAGhC,QAAQ,CAAC,KAAK,EAAE,WAAW;IAFvC,QAAQ,CAAC,IAAI,qBAAqB;gBAEb,KAAK,EAAE,WAAW;CAIxC;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/E,uCAAuC;IACvC,KAAK,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;;;;;CAGA,CAAC;AAI7B;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,WAAW,EAAE,CAAA;CAAE,GAAG,SAAS,CAaxH;AAYD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAAO,GACpD,WAAW,CAgEb"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/** A budget is used up. The reply ends with `budget_exceeded`. */
|
|
2
|
+
export class BudgetExceededError extends Error {
|
|
3
|
+
scope;
|
|
4
|
+
code = 'budget_exceeded';
|
|
5
|
+
constructor(scope) {
|
|
6
|
+
super(`The ${scope} budget is used up.`);
|
|
7
|
+
this.scope = scope;
|
|
8
|
+
this.name = 'BudgetExceededError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* What applies when a scope isn't set. Replies, not tokens, per conversation:
|
|
13
|
+
* the widget keeps a conversation across visits, and a token count there
|
|
14
|
+
* would grow with the size of your system prompt. Tokens per IP per day are
|
|
15
|
+
* the cost backstop, since a script can start new conversations at will.
|
|
16
|
+
* There's no global default, because no number fits every site.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_BUDGETS = {
|
|
19
|
+
perConversation: { messages: 200 },
|
|
20
|
+
perIpPerDay: { tokens: 2_000_000 },
|
|
21
|
+
};
|
|
22
|
+
const SCOPE_KEYS = ['perConversation', 'perIpPerDay', 'globalPerDay'];
|
|
23
|
+
/**
|
|
24
|
+
* Fills the scopes you didn't set with `DEFAULT_BUDGETS`. A scope you set
|
|
25
|
+
* replaces its default whole; `false` turns it off. `false` for the whole
|
|
26
|
+
* option turns budgets off and resolves `undefined`.
|
|
27
|
+
*/
|
|
28
|
+
export function withDefaultBudgets(budgets) {
|
|
29
|
+
if (budgets === false)
|
|
30
|
+
return undefined;
|
|
31
|
+
const out = { ...budgets };
|
|
32
|
+
const defaulted = [];
|
|
33
|
+
if (out.perConversation === undefined) {
|
|
34
|
+
out.perConversation = DEFAULT_BUDGETS.perConversation;
|
|
35
|
+
defaulted.push('conversation');
|
|
36
|
+
}
|
|
37
|
+
if (out.perIpPerDay === undefined) {
|
|
38
|
+
out.perIpPerDay = DEFAULT_BUDGETS.perIpPerDay;
|
|
39
|
+
defaulted.push('ip');
|
|
40
|
+
}
|
|
41
|
+
return SCOPE_KEYS.some((key) => out[key]) ? { budgets: out, defaulted } : undefined;
|
|
42
|
+
}
|
|
43
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
44
|
+
const CONVERSATION_TTL_MS = 30 * DAY_MS;
|
|
45
|
+
/**
|
|
46
|
+
* Token, reply and dollar budgets on a `CounterStore`, so they hold across
|
|
47
|
+
* server instances. Checked before each model request and charged after it,
|
|
48
|
+
* so a reply can go over by at most one request.
|
|
49
|
+
*/
|
|
50
|
+
export function createBudgetGuard(counters, budgets, options = {}) {
|
|
51
|
+
const prefix = options.prefix ?? 'budget';
|
|
52
|
+
const now = options.now ?? Date.now;
|
|
53
|
+
if (budgets.globalPerDay && budgets.globalPerDay.usd !== undefined && !budgets.price) {
|
|
54
|
+
throw new Error('budgets.globalPerDay.usd needs budgets.price to know what a request costs.');
|
|
55
|
+
}
|
|
56
|
+
for (const name of SCOPE_KEYS) {
|
|
57
|
+
const limit = budgets[name];
|
|
58
|
+
if (limit === undefined || limit === false)
|
|
59
|
+
continue;
|
|
60
|
+
if (typeof limit !== 'object' || limit === null)
|
|
61
|
+
throw new Error(`budgets.${name} must be an object or false.`);
|
|
62
|
+
for (const [field, value] of Object.entries(limit)) {
|
|
63
|
+
if (typeof value !== 'number' || !(value > 0))
|
|
64
|
+
throw new Error(`budgets.${name}.${field} must be a positive number.`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function scopes(context) {
|
|
68
|
+
const day = Math.floor(now() / DAY_MS);
|
|
69
|
+
const out = [];
|
|
70
|
+
if (budgets.perConversation) {
|
|
71
|
+
out.push({ scope: 'conversation', limit: budgets.perConversation, key: `${prefix}:c:${context.conversationId}`, ttlMs: CONVERSATION_TTL_MS });
|
|
72
|
+
}
|
|
73
|
+
if (budgets.perIpPerDay && context.ip) {
|
|
74
|
+
out.push({ scope: 'ip', limit: budgets.perIpPerDay, key: `${prefix}:ip:${context.ip}:${day}`, ttlMs: 2 * DAY_MS });
|
|
75
|
+
}
|
|
76
|
+
if (budgets.globalPerDay) {
|
|
77
|
+
out.push({ scope: 'global', limit: budgets.globalPerDay, key: `${prefix}:g:${day}`, ttlMs: 2 * DAY_MS });
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
const used = async (key) => (await counters.get(key))?.value ?? 0;
|
|
82
|
+
return {
|
|
83
|
+
async before(context, step) {
|
|
84
|
+
let tokensLeft;
|
|
85
|
+
for (const { scope, limit, key, ttlMs } of scopes(context)) {
|
|
86
|
+
if (limit.messages !== undefined && step === 1) {
|
|
87
|
+
// Counted when the reply starts, atomically, so a burst can't all pass.
|
|
88
|
+
const { value } = await counters.increment(`${key}:messages`, 1, ttlMs);
|
|
89
|
+
if (value > limit.messages)
|
|
90
|
+
throw new BudgetExceededError(scope);
|
|
91
|
+
}
|
|
92
|
+
if (limit.tokens !== undefined) {
|
|
93
|
+
const left = limit.tokens - (await used(`${key}:tokens`));
|
|
94
|
+
if (left <= 0)
|
|
95
|
+
throw new BudgetExceededError(scope);
|
|
96
|
+
tokensLeft = Math.min(tokensLeft ?? left, left);
|
|
97
|
+
}
|
|
98
|
+
if (limit.usd !== undefined && (await used(`${key}:usd`)) >= limit.usd) {
|
|
99
|
+
throw new BudgetExceededError(scope);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return tokensLeft === undefined ? {} : { tokensLeft };
|
|
103
|
+
},
|
|
104
|
+
async after(context, usage) {
|
|
105
|
+
const tokens = usage.inputTokens + usage.outputTokens;
|
|
106
|
+
const usd = budgets.price ? budgets.price(usage) : 0;
|
|
107
|
+
await Promise.all(scopes(context).flatMap(({ limit, key, ttlMs }) => [
|
|
108
|
+
...(limit.tokens !== undefined && tokens > 0 ? [counters.increment(`${key}:tokens`, tokens, ttlMs)] : []),
|
|
109
|
+
...(limit.usd !== undefined && usd > 0 ? [counters.increment(`${key}:usd`, usd, ttlMs)] : []),
|
|
110
|
+
]));
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/limits/budget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CounterStore } from '../storage/types.js';
|
|
2
|
+
export interface RateLimitRule {
|
|
3
|
+
/** Maximum requests allowed per window. */
|
|
4
|
+
limit: number;
|
|
5
|
+
windowMs: number;
|
|
6
|
+
}
|
|
7
|
+
export interface RateLimitResult {
|
|
8
|
+
allowed: boolean;
|
|
9
|
+
/** Requests left in the current window, never below 0. */
|
|
10
|
+
remaining: number;
|
|
11
|
+
/** When denied: how long until a request would be allowed again. */
|
|
12
|
+
retryAfterMs?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface RateLimiterOptions {
|
|
15
|
+
/** Key namespace; defaults to 'rl'. */
|
|
16
|
+
prefix?: string;
|
|
17
|
+
/** Seam for tests; defaults to Date.now. */
|
|
18
|
+
now?: () => number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sliding-window rate limiter built on two fixed-window counters.
|
|
22
|
+
*
|
|
23
|
+
* Two fixed windows — the current one and the previous one — give a smooth
|
|
24
|
+
* sliding estimate without needing per-request timestamps:
|
|
25
|
+
*
|
|
26
|
+
* estimate = prev × (1 − elapsed/windowMs) + current
|
|
27
|
+
*
|
|
28
|
+
* where `elapsed` is how far we are into the current window. At the boundary
|
|
29
|
+
* the previous window fades out linearly, so the effective limit never exceeds
|
|
30
|
+
* `limit` over any rolling window.
|
|
31
|
+
*
|
|
32
|
+
* retryAfterMs formula
|
|
33
|
+
* --------------------
|
|
34
|
+
* We want the smallest t (in ms from now) such that the estimate drops to
|
|
35
|
+
* `limit`. All denied requests have already been counted, so `current` won't
|
|
36
|
+
* change. As time advances by t, `elapsed` increases by t and `prev`'s weight
|
|
37
|
+
* shrinks:
|
|
38
|
+
*
|
|
39
|
+
* prev × (1 − (elapsed + t)/windowMs) + current ≤ limit
|
|
40
|
+
* prev × (1 − (elapsed + t)/windowMs) ≤ limit − current
|
|
41
|
+
*
|
|
42
|
+
* Rearranging (prev > 0 required, otherwise we'd already be under the limit):
|
|
43
|
+
*
|
|
44
|
+
* 1 − (elapsed + t)/windowMs ≤ (limit − current) / prev
|
|
45
|
+
* t ≥ windowMs × (1 − (limit − current) / prev) − elapsed
|
|
46
|
+
* t ≥ windowMs − elapsed − windowMs × (limit − current) / prev
|
|
47
|
+
*
|
|
48
|
+
* Clamped to [1, windowMs] to keep the estimate positive and bounded.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createRateLimiter(counters: CounterStore, rule: RateLimitRule, options?: RateLimiterOptions): {
|
|
51
|
+
check(key: string): Promise<RateLimitResult>;
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/limits/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,kBAAuB,GAC/B;IAAE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CAAE,CA0ClD"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sliding-window rate limiter built on two fixed-window counters.
|
|
3
|
+
*
|
|
4
|
+
* Two fixed windows — the current one and the previous one — give a smooth
|
|
5
|
+
* sliding estimate without needing per-request timestamps:
|
|
6
|
+
*
|
|
7
|
+
* estimate = prev × (1 − elapsed/windowMs) + current
|
|
8
|
+
*
|
|
9
|
+
* where `elapsed` is how far we are into the current window. At the boundary
|
|
10
|
+
* the previous window fades out linearly, so the effective limit never exceeds
|
|
11
|
+
* `limit` over any rolling window.
|
|
12
|
+
*
|
|
13
|
+
* retryAfterMs formula
|
|
14
|
+
* --------------------
|
|
15
|
+
* We want the smallest t (in ms from now) such that the estimate drops to
|
|
16
|
+
* `limit`. All denied requests have already been counted, so `current` won't
|
|
17
|
+
* change. As time advances by t, `elapsed` increases by t and `prev`'s weight
|
|
18
|
+
* shrinks:
|
|
19
|
+
*
|
|
20
|
+
* prev × (1 − (elapsed + t)/windowMs) + current ≤ limit
|
|
21
|
+
* prev × (1 − (elapsed + t)/windowMs) ≤ limit − current
|
|
22
|
+
*
|
|
23
|
+
* Rearranging (prev > 0 required, otherwise we'd already be under the limit):
|
|
24
|
+
*
|
|
25
|
+
* 1 − (elapsed + t)/windowMs ≤ (limit − current) / prev
|
|
26
|
+
* t ≥ windowMs × (1 − (limit − current) / prev) − elapsed
|
|
27
|
+
* t ≥ windowMs − elapsed − windowMs × (limit − current) / prev
|
|
28
|
+
*
|
|
29
|
+
* Clamped to [1, windowMs] to keep the estimate positive and bounded.
|
|
30
|
+
*/
|
|
31
|
+
export function createRateLimiter(counters, rule, options = {}) {
|
|
32
|
+
if (rule.limit <= 0)
|
|
33
|
+
throw new Error('limit must be greater than 0');
|
|
34
|
+
if (rule.windowMs <= 0)
|
|
35
|
+
throw new Error('windowMs must be greater than 0');
|
|
36
|
+
const prefix = options.prefix ?? 'rl';
|
|
37
|
+
const now = options.now ?? Date.now;
|
|
38
|
+
return {
|
|
39
|
+
async check(key) {
|
|
40
|
+
const t = now();
|
|
41
|
+
const index = Math.floor(t / rule.windowMs);
|
|
42
|
+
const elapsed = t - index * rule.windowMs;
|
|
43
|
+
// Increment the current window. TTL is 2× so the previous window
|
|
44
|
+
// stays readable for the whole next window period.
|
|
45
|
+
const currentKey = `${prefix}:${key}:${index}`;
|
|
46
|
+
const [currentEntry, prevEntry] = await Promise.all([
|
|
47
|
+
counters.increment(currentKey, 1, 2 * rule.windowMs),
|
|
48
|
+
counters.get(`${prefix}:${key}:${index - 1}`),
|
|
49
|
+
]);
|
|
50
|
+
const current = currentEntry.value;
|
|
51
|
+
const prev = prevEntry?.value ?? 0;
|
|
52
|
+
// Sliding-window estimate: blend the previous window's tail.
|
|
53
|
+
const estimate = prev * (1 - elapsed / rule.windowMs) + current;
|
|
54
|
+
if (estimate <= rule.limit) {
|
|
55
|
+
return { allowed: true, remaining: Math.max(0, Math.floor(rule.limit - estimate)) };
|
|
56
|
+
}
|
|
57
|
+
// Time (ms) until the estimate would drop back to limit.
|
|
58
|
+
// Derived above; prev > 0 whenever estimate > limit and decay is possible.
|
|
59
|
+
// When prev = 0 the counter only resets at the window boundary, so we wait
|
|
60
|
+
// out the remainder of the current window.
|
|
61
|
+
const retryAfterMs = prev > 0
|
|
62
|
+
? Math.min(rule.windowMs, Math.max(1, Math.ceil(rule.windowMs - elapsed - (rule.windowMs * (rule.limit - current)) / prev)))
|
|
63
|
+
: Math.max(1, rule.windowMs - elapsed);
|
|
64
|
+
return { allowed: false, remaining: 0, retryAfterMs };
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/limits/rate-limit.ts"],"names":[],"mappings":"AAuBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAsB,EACtB,IAAmB,EACnB,UAA8B,EAAE;IAEhC,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACrE,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IAE3E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC;IACtC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IAEpC,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,GAAW;YACrB,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;YAChB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC5C,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC;YAE1C,iEAAiE;YACjE,mDAAmD;YACnD,MAAM,UAAU,GAAG,GAAG,MAAM,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC;YAC/C,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBAClD,QAAQ,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;gBACpD,QAAQ,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI,GAAG,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;aAC9C,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC;YACnC,MAAM,IAAI,GAAG,SAAS,EAAE,KAAK,IAAI,CAAC,CAAC;YAEnC,6DAA6D;YAC7D,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC;YAEhE,IAAI,QAAQ,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC3B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC;YACtF,CAAC;YAED,yDAAyD;YACzD,2EAA2E;YAC3E,2EAA2E;YAC3E,2CAA2C;YAC3C,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC;gBAC3B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;gBAC5H,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,CAAC;YAEzC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC;QACxD,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/parts.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ChatMessage, MessagePart } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The parts of a message. Messages saved before parts existed only have
|
|
4
|
+
* `content`, which counts as a single text part (none when it's empty).
|
|
5
|
+
*/
|
|
6
|
+
export declare function partsOf(message: Pick<ChatMessage, 'content' | 'parts'>): MessagePart[];
|
|
7
|
+
/** The text parts joined, which is what `ChatMessage.content` holds. */
|
|
8
|
+
export declare function messageText(parts: readonly MessagePart[]): string;
|
|
9
|
+
//# sourceMappingURL=parts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../src/parts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAY,MAAM,YAAY,CAAC;AAErE;;;GAGG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,WAAW,EAAE,CAGtF;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAKjE"}
|
package/dist/parts.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parts of a message. Messages saved before parts existed only have
|
|
3
|
+
* `content`, which counts as a single text part (none when it's empty).
|
|
4
|
+
*/
|
|
5
|
+
export function partsOf(message) {
|
|
6
|
+
if (message.parts)
|
|
7
|
+
return message.parts;
|
|
8
|
+
return message.content ? [{ type: 'text', text: message.content }] : [];
|
|
9
|
+
}
|
|
10
|
+
/** The text parts joined, which is what `ChatMessage.content` holds. */
|
|
11
|
+
export function messageText(parts) {
|
|
12
|
+
return parts
|
|
13
|
+
.filter((part) => part.type === 'text')
|
|
14
|
+
.map((part) => part.text)
|
|
15
|
+
.join('');
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=parts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parts.js","sourceRoot":"","sources":["../src/parts.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,OAA+C;IACrE,IAAI,OAAO,CAAC,KAAK;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC;IACxC,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC1E,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,WAAW,CAAC,KAA6B;IACvD,OAAO,KAAK;SACT,MAAM,CAAC,CAAC,IAAI,EAAoB,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;SACxD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;SACxB,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { WirePart } from './v1.js';
|
|
2
|
+
/**
|
|
3
|
+
* The AI SDK's UI message stream (what `useChat` reads), so an app built on
|
|
4
|
+
* the AI SDK can point at a ChatCops server. Only the mapping lives here, with
|
|
5
|
+
* no dependency on the `ai` package; `chatcopsAiSdkHandler` in
|
|
6
|
+
* `@chatcops/server` serves it. A contract test pins it to an `ai` version.
|
|
7
|
+
*/
|
|
8
|
+
/** One UI message stream chunk. See `UIMessageChunk` in the `ai` package. */
|
|
9
|
+
export type AiSdkChunk = {
|
|
10
|
+
type: string;
|
|
11
|
+
} & Record<string, unknown>;
|
|
12
|
+
/** The stream header `useChat` checks for. */
|
|
13
|
+
export declare const AI_SDK_STREAM_HEADER = "x-vercel-ai-ui-message-stream";
|
|
14
|
+
/**
|
|
15
|
+
* Turns protocol v1 parts into UI message stream chunks, one reply at a
|
|
16
|
+
* time: it remembers which tools ran on the server and whether the reply was
|
|
17
|
+
* filtered.
|
|
18
|
+
*/
|
|
19
|
+
export declare function aiSdkEncoder(): (part: WirePart) => AiSdkChunk[];
|
|
20
|
+
/**
|
|
21
|
+
* The ChatCops request for a `useChat` request (`{ id, messages, trigger,
|
|
22
|
+
* messageId }`). Only the last user message is read: the server keeps the
|
|
23
|
+
* history, so a client can't add turns the model never wrote. `pageContext`,
|
|
24
|
+
* `locale` and `userData` pass through from the request body.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fromAiSdkRequest(body: unknown): Record<string, unknown>;
|
|
27
|
+
//# sourceMappingURL=ai-sdk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sdk.d.ts","sourceRoot":"","sources":["../../src/protocol/ai-sdk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE1D;;;;;GAKG;AAEH,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpE,8CAA8C;AAC9C,eAAO,MAAM,oBAAoB,kCAAkC,CAAC;AAWpE;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,CAAC,IAAI,EAAE,QAAQ,KAAK,UAAU,EAAE,CA+D/D;AAQD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwBvE"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/** The stream header `useChat` checks for. */
|
|
2
|
+
export const AI_SDK_STREAM_HEADER = 'x-vercel-ai-ui-message-stream';
|
|
3
|
+
const FINISH_REASONS = {
|
|
4
|
+
stop: 'stop',
|
|
5
|
+
length: 'length',
|
|
6
|
+
error: 'error',
|
|
7
|
+
'client-tool-calls': 'tool-calls',
|
|
8
|
+
'approval-required': 'tool-calls',
|
|
9
|
+
handoff: 'other',
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Turns protocol v1 parts into UI message stream chunks, one reply at a
|
|
13
|
+
* time: it remembers which tools ran on the server and whether the reply was
|
|
14
|
+
* filtered.
|
|
15
|
+
*/
|
|
16
|
+
export function aiSdkEncoder() {
|
|
17
|
+
const serverTools = new Set();
|
|
18
|
+
let filtered = false;
|
|
19
|
+
return (part) => {
|
|
20
|
+
switch (part.type) {
|
|
21
|
+
case 'start':
|
|
22
|
+
return [
|
|
23
|
+
{ type: 'start', messageId: part.messageId, messageMetadata: { conversationId: part.conversationId } },
|
|
24
|
+
{ type: 'start-step' },
|
|
25
|
+
];
|
|
26
|
+
case 'text-start':
|
|
27
|
+
case 'text-end':
|
|
28
|
+
return [{ type: part.type, id: part.id }];
|
|
29
|
+
case 'text-delta':
|
|
30
|
+
return [{ type: 'text-delta', id: part.id, delta: part.delta }];
|
|
31
|
+
case 'source':
|
|
32
|
+
return [{ type: 'source-url', sourceId: part.sourceId, url: part.url, ...(part.title ? { title: part.title } : {}) }];
|
|
33
|
+
case 'tool-call': {
|
|
34
|
+
// A tool the server ran: `useChat` must not run it or wait for its output.
|
|
35
|
+
const server = part.executor === 'server';
|
|
36
|
+
if (server)
|
|
37
|
+
serverTools.add(part.toolCallId);
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
type: 'tool-input-available',
|
|
41
|
+
toolCallId: part.toolCallId,
|
|
42
|
+
toolName: part.toolName,
|
|
43
|
+
input: part.input ?? {},
|
|
44
|
+
...(server ? { providerExecuted: true } : {}),
|
|
45
|
+
...(part.title ? { title: part.title } : {}),
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
case 'tool-result': {
|
|
50
|
+
const executed = serverTools.has(part.toolCallId) ? { providerExecuted: true } : {};
|
|
51
|
+
return part.error !== undefined
|
|
52
|
+
? [{ type: 'tool-output-error', toolCallId: part.toolCallId, errorText: part.error, ...executed }]
|
|
53
|
+
: [{ type: 'tool-output-available', toolCallId: part.toolCallId, output: part.output ?? null, ...executed }];
|
|
54
|
+
}
|
|
55
|
+
case 'lead-captured':
|
|
56
|
+
return [{ type: 'data-lead-captured', data: part.lead }];
|
|
57
|
+
case 'suggestions':
|
|
58
|
+
return [{ type: 'data-suggestions', data: part.items }];
|
|
59
|
+
case 'handoff':
|
|
60
|
+
case 'agent-message': {
|
|
61
|
+
const { type, ...data } = part;
|
|
62
|
+
return [{ type: `data-${type}`, data }];
|
|
63
|
+
}
|
|
64
|
+
case 'error':
|
|
65
|
+
if (part.code === 'content_filtered')
|
|
66
|
+
filtered = true;
|
|
67
|
+
return [{ type: 'error', errorText: part.message ?? part.code }];
|
|
68
|
+
case 'finish': {
|
|
69
|
+
if (part.reason === 'aborted')
|
|
70
|
+
return [{ type: 'abort', reason: 'aborted' }];
|
|
71
|
+
const finishReason = filtered ? 'content-filter' : FINISH_REASONS[part.reason];
|
|
72
|
+
return [
|
|
73
|
+
{ type: 'finish-step' },
|
|
74
|
+
{ type: 'finish', finishReason, ...(part.usage ? { messageMetadata: { usage: part.usage } } : {}) },
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
default:
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The ChatCops request for a `useChat` request (`{ id, messages, trigger,
|
|
84
|
+
* messageId }`). Only the last user message is read: the server keeps the
|
|
85
|
+
* history, so a client can't add turns the model never wrote. `pageContext`,
|
|
86
|
+
* `locale` and `userData` pass through from the request body.
|
|
87
|
+
*/
|
|
88
|
+
export function fromAiSdkRequest(body) {
|
|
89
|
+
if (body === null || typeof body !== 'object')
|
|
90
|
+
return {};
|
|
91
|
+
const { id, messages, trigger, messageId, pageContext, locale, userData } = body;
|
|
92
|
+
const list = Array.isArray(messages) ? messages : [];
|
|
93
|
+
const lastUser = [...list].reverse().find((message) => message?.role === 'user');
|
|
94
|
+
const request = { conversationId: id, message: textOf(lastUser) };
|
|
95
|
+
if (trigger === 'regenerate-message') {
|
|
96
|
+
if (typeof messageId !== 'string') {
|
|
97
|
+
request.regenerate = true;
|
|
98
|
+
}
|
|
99
|
+
else if (list.some((message) => message?.id === messageId)) {
|
|
100
|
+
// `regenerate({ messageId })` on a user message keeps it in `messages`.
|
|
101
|
+
request.regenerate = true;
|
|
102
|
+
request.messageId = messageId;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
request.regenerate = { assistantMessageId: messageId };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (typeof lastUser?.id === 'string') {
|
|
109
|
+
request.messageId = lastUser.id;
|
|
110
|
+
}
|
|
111
|
+
if (pageContext !== undefined)
|
|
112
|
+
request.pageContext = pageContext;
|
|
113
|
+
if (locale !== undefined)
|
|
114
|
+
request.locale = locale;
|
|
115
|
+
if (userData !== undefined)
|
|
116
|
+
request.userData = userData;
|
|
117
|
+
return request;
|
|
118
|
+
}
|
|
119
|
+
function textOf(message) {
|
|
120
|
+
if (!Array.isArray(message?.parts))
|
|
121
|
+
return '';
|
|
122
|
+
return message.parts
|
|
123
|
+
.filter((part) => part?.type === 'text' && typeof part.text === 'string')
|
|
124
|
+
.map((part) => part.text)
|
|
125
|
+
.join('\n\n');
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=ai-sdk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sdk.js","sourceRoot":"","sources":["../../src/protocol/ai-sdk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { WireErrorCode, WireErrorBody } from './v1.js';
|
|
3
|
+
export declare function statusForError(code: WireErrorCode): number;
|
|
4
|
+
export declare function errorBody(code: WireErrorCode, options?: {
|
|
5
|
+
message?: string;
|
|
6
|
+
retryAfterMs?: number;
|
|
7
|
+
}): WireErrorBody;
|
|
8
|
+
/** "Invalid request: message, pageContext.url". Field paths only, never values. */
|
|
9
|
+
export declare function describeInvalidRequest(error: z.ZodError): string;
|
|
10
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/protocol/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAqB5D,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAE1D;AAED,wBAAgB,SAAS,CACvB,IAAI,EAAE,aAAa,EACnB,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD,aAAa,CASf;AAED,mFAAmF;AACnF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG,MAAM,CAQhE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const STATUS_BY_CODE = {
|
|
2
|
+
invalid_request: 400,
|
|
3
|
+
content_filtered: 400,
|
|
4
|
+
session_required: 401,
|
|
5
|
+
token_expired: 401,
|
|
6
|
+
conversation_mismatch: 401,
|
|
7
|
+
turnstile_failed: 401,
|
|
8
|
+
origin_not_allowed: 403,
|
|
9
|
+
conversation_busy: 409,
|
|
10
|
+
payload_too_large: 413,
|
|
11
|
+
rate_limited: 429,
|
|
12
|
+
budget_exceeded: 429,
|
|
13
|
+
tool_error: 500,
|
|
14
|
+
internal_error: 500,
|
|
15
|
+
provider_error: 502,
|
|
16
|
+
provider_unavailable: 503,
|
|
17
|
+
timeout: 504,
|
|
18
|
+
};
|
|
19
|
+
export function statusForError(code) {
|
|
20
|
+
return STATUS_BY_CODE[code];
|
|
21
|
+
}
|
|
22
|
+
export function errorBody(code, options) {
|
|
23
|
+
const error = { code };
|
|
24
|
+
if (options?.message !== undefined) {
|
|
25
|
+
error.message = options.message;
|
|
26
|
+
}
|
|
27
|
+
if (options?.retryAfterMs !== undefined) {
|
|
28
|
+
error.retryAfterMs = options.retryAfterMs;
|
|
29
|
+
}
|
|
30
|
+
return { error };
|
|
31
|
+
}
|
|
32
|
+
/** "Invalid request: message, pageContext.url". Field paths only, never values. */
|
|
33
|
+
export function describeInvalidRequest(error) {
|
|
34
|
+
const paths = new Set();
|
|
35
|
+
for (const issue of error.issues) {
|
|
36
|
+
paths.add(issue.path.length > 0 ? issue.path.join('.') : '(root)');
|
|
37
|
+
}
|
|
38
|
+
return paths.size > 0
|
|
39
|
+
? `Invalid request: ${Array.from(paths).join(', ')}`
|
|
40
|
+
: 'Invalid request';
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=errors.js.map
|