@mastra/code-sdk 1.10.1 → 1.11.0-alpha.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/dist/agents/mastracode-gateway.d.ts +4 -1
- package/dist/agents/mastracode-gateway.d.ts.map +1 -1
- package/dist/agents/mastracode-gateway.js +13 -9
- package/dist/agents/mastracode-gateway.js.map +1 -1
- package/dist/agents/memory.d.ts.map +1 -1
- package/dist/agents/memory.js +7 -2
- package/dist/agents/memory.js.map +1 -1
- package/dist/agents/model.d.ts +2 -0
- package/dist/agents/model.d.ts.map +1 -1
- package/dist/agents/model.js +1 -0
- package/dist/agents/model.js.map +1 -1
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -8
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic-prompt-cache.d.ts +19 -0
- package/dist/providers/anthropic-prompt-cache.d.ts.map +1 -0
- package/dist/providers/anthropic-prompt-cache.js +11 -0
- package/dist/providers/anthropic-prompt-cache.js.map +1 -0
- package/dist/providers/claude-max.d.ts +4 -2
- package/dist/providers/claude-max.d.ts.map +1 -1
- package/dist/providers/claude-max.js +8 -7
- package/dist/providers/claude-max.js.map +1 -1
- package/dist/utils/notification-dispatch.d.ts +59 -0
- package/dist/utils/notification-dispatch.d.ts.map +1 -0
- package/dist/utils/notification-dispatch.js +109 -0
- package/dist/utils/notification-dispatch.js.map +1 -0
- package/dist/utils/signals-pubsub.d.ts +42 -13
- package/dist/utils/signals-pubsub.d.ts.map +1 -1
- package/dist/utils/signals-pubsub.js +141 -38
- package/dist/utils/signals-pubsub.js.map +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/providers/anthropic-prompt-cache.ts
|
|
2
|
+
/**
|
|
3
|
+
* TTL for the main agent's Anthropic prompt-cache breakpoints.
|
|
4
|
+
* Observational memory's idle activation for Anthropic is set to the same value,
|
|
5
|
+
* so buffered observations don't activate while the cache is still warm.
|
|
6
|
+
*/
|
|
7
|
+
const ANTHROPIC_PROMPT_CACHE_TTL = "1h";
|
|
8
|
+
//#endregion
|
|
9
|
+
export { ANTHROPIC_PROMPT_CACHE_TTL };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=anthropic-prompt-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-prompt-cache.js","names":[],"sources":["../../src/providers/anthropic-prompt-cache.ts"],"sourcesContent":["/**\n * TTL for the main agent's Anthropic prompt-cache breakpoints.\n * Observational memory's idle activation for Anthropic is set to the same value,\n * so buffered observations don't activate while the cache is still warm.\n */\nexport const ANTHROPIC_PROMPT_CACHE_TTL = '1h' as const;\n\n/**\n * Which Anthropic prompt-cache breakpoints a model writes.\n *\n * - `conversation`: the last system message and the latest message, with\n * {@link ANTHROPIC_PROMPT_CACHE_TTL}. For the main agent, whose next request\n * extends the same conversation.\n * - `system`: the last system message only, with a 5-minute TTL. For background\n * calls (observer, reflector, thread titles) that share instructions across\n * calls but send different content each time, so caching the content is a\n * write that is never read.\n */\nexport type AnthropicPromptCacheScope = 'conversation' | 'system';\n"],"mappings":";;;;;;AAKA,MAAa,6BAA6B"}
|
|
@@ -8,6 +8,7 @@ import type { MastraModelConfig } from '@mastra/core/llm';
|
|
|
8
8
|
import type { LanguageModelMiddleware } from 'ai';
|
|
9
9
|
import { AuthStorage } from '../auth/storage.js';
|
|
10
10
|
import type { CredentialStore } from '../auth/types.js';
|
|
11
|
+
import type { AnthropicPromptCacheScope } from './anthropic-prompt-cache.js';
|
|
11
12
|
import type { ThinkingLevel } from './openai-codex.js';
|
|
12
13
|
/**
|
|
13
14
|
* Get or create the shared AuthStorage instance
|
|
@@ -27,13 +28,13 @@ export declare const claudeCodeMiddleware: LanguageModelMiddleware;
|
|
|
27
28
|
*
|
|
28
29
|
* Adds cache breakpoints at strategic locations:
|
|
29
30
|
* 1. Last system message (end of static instructions + dynamic memory)
|
|
30
|
-
* 2. Most recent user/assistant message (conversation context)
|
|
31
|
+
* 2. Most recent user/assistant message (conversation context), `conversation` scope only
|
|
31
32
|
*
|
|
32
33
|
* This allows Anthropic to cache:
|
|
33
34
|
* - System prompts and instructions (rarely change)
|
|
34
35
|
* - Conversation history up to the last message
|
|
35
36
|
*/
|
|
36
|
-
export declare const
|
|
37
|
+
export declare const createPromptCacheMiddleware: (scope: AnthropicPromptCacheScope) => LanguageModelMiddleware;
|
|
37
38
|
/**
|
|
38
39
|
* Middleware that maps the session thinking level onto Anthropic extended
|
|
39
40
|
* thinking. Returns `undefined` (no middleware) when the level is `off` or the
|
|
@@ -61,5 +62,6 @@ export declare function opencodeClaudeMaxProvider(modelId?: string, options?: {
|
|
|
61
62
|
headers?: Record<string, string>;
|
|
62
63
|
authStorage?: CredentialStore;
|
|
63
64
|
thinkingLevel?: ThinkingLevel;
|
|
65
|
+
promptCacheScope?: AnthropicPromptCacheScope;
|
|
64
66
|
}): MastraModelConfig;
|
|
65
67
|
//# sourceMappingURL=claude-max.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-max.d.ts","sourceRoot":"","sources":["../../src/providers/claude-max.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,IAAI,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"claude-max.d.ts","sourceRoot":"","sources":["../../src/providers/claude-max.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,IAAI,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAoBvD;;GAEG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAK5C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAErE;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,uBAkBlC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,GAAI,OAAO,yBAAyB,KAAG,uBA8D7E,CAAC;AA+CH;;;;;;;;;GASG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,MAAM,EACf,aAAa,CAAC,EAAE,aAAa,GAC5B,uBAAuB,GAAG,SAAS,CAiCrC;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,eAAe,CAAA;CAAO,GAAG,OAAO,KAAK,CAuCnG;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,MAAmC,EAC5C,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,gBAAgB,CAAC,EAAE,yBAAyB,CAAC;CAC9C,GACA,iBAAiB,CAgCnB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AuthStorage } from "../auth/storage.js";
|
|
2
2
|
import { ProviderAuthRequiredError } from "../auth/provider-auth-error.js";
|
|
3
|
+
import "./anthropic-prompt-cache.js";
|
|
3
4
|
import { wrapLanguageModel } from "ai";
|
|
4
5
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
5
6
|
//#region src/providers/claude-max.ts
|
|
@@ -53,19 +54,19 @@ const claudeCodeMiddleware = {
|
|
|
53
54
|
*
|
|
54
55
|
* Adds cache breakpoints at strategic locations:
|
|
55
56
|
* 1. Last system message (end of static instructions + dynamic memory)
|
|
56
|
-
* 2. Most recent user/assistant message (conversation context)
|
|
57
|
+
* 2. Most recent user/assistant message (conversation context), `conversation` scope only
|
|
57
58
|
*
|
|
58
59
|
* This allows Anthropic to cache:
|
|
59
60
|
* - System prompts and instructions (rarely change)
|
|
60
61
|
* - Conversation history up to the last message
|
|
61
62
|
*/
|
|
62
|
-
const
|
|
63
|
+
const createPromptCacheMiddleware = (scope) => ({
|
|
63
64
|
specificationVersion: "v3",
|
|
64
65
|
transformParams: async ({ params }) => {
|
|
65
66
|
const prompt = [...params.prompt];
|
|
66
67
|
const cacheControl = {
|
|
67
68
|
type: "ephemeral",
|
|
68
|
-
ttl: "5m"
|
|
69
|
+
ttl: scope === "conversation" ? "1h" : "5m"
|
|
69
70
|
};
|
|
70
71
|
const addCacheToMessage = (msg) => {
|
|
71
72
|
if (typeof msg.content === "string") return {
|
|
@@ -105,13 +106,13 @@ const promptCacheMiddleware = {
|
|
|
105
106
|
}
|
|
106
107
|
if (lastSystemIdx >= 0) prompt[lastSystemIdx] = addCacheToMessage(prompt[lastSystemIdx]);
|
|
107
108
|
const lastIdx = prompt.length - 1;
|
|
108
|
-
if (lastIdx >= 0 && lastIdx !== lastSystemIdx) prompt[lastIdx] = addCacheToMessage(prompt[lastIdx]);
|
|
109
|
+
if (scope === "conversation" && lastIdx >= 0 && lastIdx !== lastSystemIdx) prompt[lastIdx] = addCacheToMessage(prompt[lastIdx]);
|
|
109
110
|
return {
|
|
110
111
|
...params,
|
|
111
112
|
prompt
|
|
112
113
|
};
|
|
113
114
|
}
|
|
114
|
-
};
|
|
115
|
+
});
|
|
115
116
|
const ANTHROPIC_EFFORT = {
|
|
116
117
|
low: "low",
|
|
117
118
|
medium: "medium",
|
|
@@ -226,7 +227,7 @@ function opencodeClaudeMaxProvider(modelId = "claude-sonnet-4-20250514", options
|
|
|
226
227
|
const thinkingMiddleware = createAnthropicThinkingMiddleware(modelId, options?.thinkingLevel);
|
|
227
228
|
const middleware = [
|
|
228
229
|
claudeCodeMiddleware,
|
|
229
|
-
|
|
230
|
+
createPromptCacheMiddleware(options?.promptCacheScope ?? "conversation"),
|
|
230
231
|
...thinkingMiddleware ? [thinkingMiddleware] : []
|
|
231
232
|
];
|
|
232
233
|
if (process.env.NODE_ENV === "test" || process.env.VITEST) return wrapLanguageModel({
|
|
@@ -246,6 +247,6 @@ function opencodeClaudeMaxProvider(modelId = "claude-sonnet-4-20250514", options
|
|
|
246
247
|
});
|
|
247
248
|
}
|
|
248
249
|
//#endregion
|
|
249
|
-
export { buildAnthropicOAuthFetch, claudeCodeMiddleware, createAnthropicThinkingMiddleware, getAuthStorage, opencodeClaudeMaxProvider,
|
|
250
|
+
export { buildAnthropicOAuthFetch, claudeCodeMiddleware, createAnthropicThinkingMiddleware, createPromptCacheMiddleware, getAuthStorage, opencodeClaudeMaxProvider, setAuthStorage };
|
|
250
251
|
|
|
251
252
|
//# sourceMappingURL=claude-max.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-max.js","names":[],"sources":["../../src/providers/claude-max.ts"],"sourcesContent":["/**\n * Claude Max OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with Claude Max plan.\n * The OAuth endpoint requires a specific system message to be present.\n */\n\nimport { createAnthropic } from '@ai-sdk/anthropic';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport { wrapLanguageModel } from 'ai';\nimport type { LanguageModelMiddleware } from 'ai';\nimport { ProviderAuthRequiredError } from '../auth/provider-auth-error.js';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\nimport type { ThinkingLevel } from './openai-codex.js';\n\n// Required for Claude Max plan OAuth - the endpoint checks for this system message\nconst claudeCodeIdentity = \"You are Claude Code, Anthropic's official CLI for Claude.\";\n\n// Betas required for Claude Max plan OAuth. Merged with (not replacing) any\n// betas the AI SDK already set on the request — e.g. the SDK adds\n// `server-side-fallback-2026-06-01` when `providerOptions.anthropic.fallbacks`\n// is configured; dropping it makes the API reject the `fallbacks` body field\n// with \"Extra inputs are not permitted\".\nconst OAUTH_REQUIRED_BETAS = [\n 'oauth-2025-04-20',\n 'claude-code-20250219',\n 'interleaved-thinking-2025-05-14',\n 'fine-grained-tool-streaming-2025-05-14',\n];\n\n// Singleton auth storage instance\nlet authStorageInstance: AuthStorage | null = null;\n\n/**\n * Get or create the shared AuthStorage instance\n */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/**\n * Set a custom AuthStorage instance (useful for TUI integration)\n */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n/**\n * Middleware that injects the Claude Code identity system message\n * Required for Claude Max OAuth authentication\n */\nexport const claudeCodeMiddleware: LanguageModelMiddleware = {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n // Prepend the Claude Code identity as the first system message\n const systemMessage = {\n role: 'system' as const,\n content: claudeCodeIdentity,\n };\n\n if (params.temperature) {\n delete params.topP;\n }\n\n return {\n ...params,\n prompt: [systemMessage, ...params.prompt],\n };\n },\n};\n\n/**\n * Prompt caching middleware for Anthropic\n *\n * Adds cache breakpoints at strategic locations:\n * 1. Last system message (end of static instructions + dynamic memory)\n * 2. Most recent user/assistant message (conversation context)\n *\n * This allows Anthropic to cache:\n * - System prompts and instructions (rarely change)\n * - Conversation history up to the last message\n */\nexport const promptCacheMiddleware: LanguageModelMiddleware = {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n const prompt = [...params.prompt];\n\n const cacheControl = { type: 'ephemeral' as const, ttl: '5m' as const };\n\n // Helper to add cache control to a message's last content part\n const addCacheToMessage = (msg: any) => {\n // For system messages with string content\n if (typeof msg.content === 'string') {\n return {\n ...msg,\n providerOptions: {\n ...msg.providerOptions,\n anthropic: { ...msg.providerOptions?.anthropic, cacheControl },\n },\n };\n }\n\n // For messages with array content, add to last part\n if (Array.isArray(msg.content) && msg.content.length > 0) {\n const content = [...msg.content];\n const lastPart = content[content.length - 1];\n content[content.length - 1] = {\n ...lastPart,\n providerOptions: {\n ...lastPart.providerOptions,\n anthropic: { ...lastPart.providerOptions?.anthropic, cacheControl },\n },\n };\n return { ...msg, content };\n }\n\n return msg;\n };\n\n // Find the last system message index\n let lastSystemIdx = -1;\n for (let i = prompt.length - 1; i >= 0; i--) {\n if ((prompt[i] as any).role === 'system') {\n lastSystemIdx = i;\n break;\n }\n }\n\n // Add cache breakpoint to last system message\n if (lastSystemIdx >= 0) {\n prompt[lastSystemIdx] = addCacheToMessage(prompt[lastSystemIdx]);\n }\n\n // Add cache breakpoint to the most recent message (last in array)\n const lastIdx = prompt.length - 1;\n if (lastIdx >= 0 && lastIdx !== lastSystemIdx) {\n prompt[lastIdx] = addCacheToMessage(prompt[lastIdx]);\n }\n\n return { ...params, prompt };\n },\n};\n\ntype ActiveThinkingLevel = Exclude<ThinkingLevel, 'off'>;\n\n// Anthropic's effort scale matches mastracode thinking levels 1:1 (minus 'off'),\n// including 'max' — the level OpenAI's scale stops short of.\nconst ANTHROPIC_EFFORT: Record<ActiveThinkingLevel, 'low' | 'medium' | 'high' | 'xhigh' | 'max'> = {\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'xhigh',\n max: 'max',\n};\n\nconst ANTHROPIC_XHIGH_EFFORT_RE = /claude-(?:opus-4-[78]|opus-5|sonnet-5|fable-5)/;\n\nfunction getAnthropicEffort(modelId: string, level: ActiveThinkingLevel) {\n if (level === 'xhigh' && !ANTHROPIC_XHIGH_EFFORT_RE.test(modelId)) return 'high';\n return ANTHROPIC_EFFORT[level];\n}\n\n// Extended-thinking budgets for models that predate adaptive thinking/effort.\n// Budgets count toward max_tokens, so they stay well below the smallest\n// output ceiling of the budget-era models (32k on Opus 4.0/4.1).\nconst ANTHROPIC_THINKING_BUDGET_TOKENS: Record<ActiveThinkingLevel, number> = {\n low: 4096,\n medium: 8192,\n high: 16384,\n xhigh: 24576,\n max: 24576,\n};\n\n/** Claude generations that support adaptive thinking + `output_config.effort`. */\nconst ADAPTIVE_THINKING_RE = /claude-(?:sonnet-4-6|opus-4-[678]|opus-5|sonnet-5|fable-5)/;\n/** Older generations that support extended thinking via `budget_tokens`. */\nconst BUDGET_THINKING_RE = /claude-(?:3-7|sonnet-4|opus-4|haiku-4-5)/;\n/** Generations with no extended-thinking support at all. */\nconst NO_THINKING_RE = /claude-(?:instant|v?2(?:[-.:]|$)|3(?:[-.]|$)|3-5)/;\n\nfunction getAnthropicThinkingCapability(modelId: string): 'adaptive' | 'budget' | 'none' {\n if (ADAPTIVE_THINKING_RE.test(modelId)) return 'adaptive';\n if (BUDGET_THINKING_RE.test(modelId)) return 'budget';\n if (NO_THINKING_RE.test(modelId)) return 'none';\n // Unknown (i.e. newer) Claude models: assume the current API surface.\n return 'adaptive';\n}\n\n/**\n * Middleware that maps the session thinking level onto Anthropic extended\n * thinking. Returns `undefined` (no middleware) when the level is `off` or the\n * model doesn't support thinking, preserving the previous request shape.\n *\n * - Adaptive-era models (Sonnet 4.6+/Opus 4.6+): `thinking: adaptive` plus\n * `output_config.effort` mapped 1:1 from the level (including `max`).\n * - Budget-era models (Claude 3.7 – Opus 4.5): `thinking: enabled` with a\n * `budget_tokens` value derived from the level.\n */\nexport function createAnthropicThinkingMiddleware(\n modelId: string,\n thinkingLevel?: ThinkingLevel,\n): LanguageModelMiddleware | undefined {\n if (!thinkingLevel || thinkingLevel === 'off') return undefined;\n const capability = getAnthropicThinkingCapability(modelId);\n if (capability === 'none') return undefined;\n const level = thinkingLevel as ActiveThinkingLevel;\n\n return {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n const anthropicOptions = (params.providerOptions?.anthropic ?? {}) as Record<string, unknown>;\n // Don't override explicit per-request thinking configuration.\n if (anthropicOptions.thinking !== undefined || anthropicOptions.effort !== undefined) {\n return params;\n }\n\n // Anthropic rejects sampling parameters when thinking is enabled.\n delete params.temperature;\n delete params.topP;\n delete params.topK;\n\n params.providerOptions = {\n ...params.providerOptions,\n anthropic: {\n ...anthropicOptions,\n ...(capability === 'adaptive'\n ? { thinking: { type: 'adaptive', display: 'summarized' }, effort: getAnthropicEffort(modelId, level) }\n : { thinking: { type: 'enabled', budgetTokens: ANTHROPIC_THINKING_BUDGET_TOKENS[level] } }),\n },\n } as typeof params.providerOptions;\n\n return params;\n },\n };\n}\n\n/**\n * Build a fetch function that handles Anthropic OAuth.\n * Preserves non-auth headers from init (critical for gateway auth header to survive\n * when used with the gateway). Strips `authorization` and `x-api-key`.\n */\nexport function buildAnthropicOAuthFetch(opts: { authStorage?: CredentialStore } = {}): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const storedCred = storage.get('anthropic');\n if (storedCred?.type === 'api_key') {\n throw new Error('Anthropic API key credential is configured, but OAuth is required.');\n }\n\n const accessToken = await storage.getApiKey('anthropic');\n if (!accessToken) {\n throw new ProviderAuthRequiredError('Not logged in to Anthropic.');\n }\n\n // Preserve Request headers and let explicit init headers override them.\n const headers = new Headers(url instanceof Request ? url.headers : undefined);\n if (init?.headers) new Headers(init.headers).forEach((value, key) => headers.set(key, value));\n headers.delete('authorization');\n headers.delete('x-api-key');\n headers.set('Authorization', `Bearer ${accessToken}`);\n const requestBetas = (headers.get('anthropic-beta') ?? '')\n .split(',')\n .map(beta => beta.trim())\n .filter(Boolean);\n headers.set('anthropic-beta', Array.from(new Set([...OAUTH_REQUIRED_BETAS, ...requestBetas])).join(','));\n headers.set('anthropic-version', '2023-06-01');\n\n try {\n return await fetch(url, { ...init, headers });\n } catch (error) {\n if (error && typeof error === 'object') {\n Object.assign(error as Record<string, unknown>, {\n requestUrl: url instanceof URL ? url.toString() : typeof url === 'string' ? url : url.url,\n });\n }\n throw error;\n }\n }) as typeof fetch;\n}\n\n/**\n * Creates an Anthropic model using Claude Max OAuth authentication\n * Uses OAuth tokens from AuthStorage (auto-refreshes when needed)\n */\nexport function opencodeClaudeMaxProvider(\n modelId: string = 'claude-sonnet-4-20250514',\n options?: { headers?: Record<string, string>; authStorage?: CredentialStore; thinkingLevel?: ThinkingLevel },\n): MastraModelConfig {\n const headers = options?.headers;\n const thinkingMiddleware = createAnthropicThinkingMiddleware(modelId, options?.thinkingLevel);\n const middleware = [claudeCodeMiddleware, promptCacheMiddleware, ...(thinkingMiddleware ? [thinkingMiddleware] : [])];\n\n // Test environment: use API key\n if (process.env.NODE_ENV === 'test' || process.env.VITEST) {\n const anthropic = createAnthropic({\n apiKey: 'test-api-key',\n headers,\n });\n return wrapLanguageModel({\n model: anthropic(modelId),\n middleware,\n });\n }\n\n const anthropic = createAnthropic({\n apiKey: 'oauth-placeholder',\n headers,\n fetch: buildAnthropicOAuthFetch({ authStorage: options?.authStorage }) as any,\n });\n\n // Wrap with middleware to inject Claude Code identity and enable prompt caching\n return wrapLanguageModel({\n model: anthropic(modelId),\n middleware,\n });\n}\n"],"mappings":";;;;;;;;;;;AAiBA,MAAM,qBAAqB;AAO3B,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;AACF;AAGA,IAAI,sBAA0C;;;;AAK9C,SAAgB,iBAA8B;CAC5C,IAAI,CAAC,qBACH,sBAAsB,IAAI,YAAY;CAExC,OAAO;AACT;;;;AAKA,SAAgB,eAAe,SAAwC;CACrE,sBAAsB,WAAW;AACnC;;;;;AAMA,MAAa,uBAAgD;CAC3D,sBAAsB;CACtB,iBAAiB,OAAO,EAAE,aAAa;EAErC,MAAM,gBAAgB;GACpB,MAAM;GACN,SAAS;EACX;EAEA,IAAI,OAAO,aACT,OAAO,OAAO;EAGhB,OAAO;GACL,GAAG;GACH,QAAQ,CAAC,eAAe,GAAG,OAAO,MAAM;EAC1C;CACF;AACF;;;;;;;;;;;;AAaA,MAAa,wBAAiD;CAC5D,sBAAsB;CACtB,iBAAiB,OAAO,EAAE,aAAa;EACrC,MAAM,SAAS,CAAC,GAAG,OAAO,MAAM;EAEhC,MAAM,eAAe;GAAE,MAAM;GAAsB,KAAK;EAAc;EAGtE,MAAM,qBAAqB,QAAa;GAEtC,IAAI,OAAO,IAAI,YAAY,UACzB,OAAO;IACL,GAAG;IACH,iBAAiB;KACf,GAAG,IAAI;KACP,WAAW;MAAE,GAAG,IAAI,iBAAiB;MAAW;KAAa;IAC/D;GACF;GAIF,IAAI,MAAM,QAAQ,IAAI,OAAO,KAAK,IAAI,QAAQ,SAAS,GAAG;IACxD,MAAM,UAAU,CAAC,GAAG,IAAI,OAAO;IAC/B,MAAM,WAAW,QAAQ,QAAQ,SAAS;IAC1C,QAAQ,QAAQ,SAAS,KAAK;KAC5B,GAAG;KACH,iBAAiB;MACf,GAAG,SAAS;MACZ,WAAW;OAAE,GAAG,SAAS,iBAAiB;OAAW;MAAa;KACpE;IACF;IACA,OAAO;KAAE,GAAG;KAAK;IAAQ;GAC3B;GAEA,OAAO;EACT;EAGA,IAAI,gBAAgB;EACpB,KAAK,IAAI,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KACtC,IAAK,OAAO,EAAE,CAAS,SAAS,UAAU;GACxC,gBAAgB;GAChB;EACF;EAIF,IAAI,iBAAiB,GACnB,OAAO,iBAAiB,kBAAkB,OAAO,cAAc;EAIjE,MAAM,UAAU,OAAO,SAAS;EAChC,IAAI,WAAW,KAAK,YAAY,eAC9B,OAAO,WAAW,kBAAkB,OAAO,QAAQ;EAGrD,OAAO;GAAE,GAAG;GAAQ;EAAO;CAC7B;AACF;AAMA,MAAM,mBAA6F;CACjG,KAAK;CACL,QAAQ;CACR,MAAM;CACN,OAAO;CACP,KAAK;AACP;AAEA,MAAM,4BAA4B;AAElC,SAAS,mBAAmB,SAAiB,OAA4B;CACvE,IAAI,UAAU,WAAW,CAAC,0BAA0B,KAAK,OAAO,GAAG,OAAO;CAC1E,OAAO,iBAAiB;AAC1B;AAKA,MAAM,mCAAwE;CAC5E,KAAK;CACL,QAAQ;CACR,MAAM;CACN,OAAO;CACP,KAAK;AACP;;AAGA,MAAM,uBAAuB;;AAE7B,MAAM,qBAAqB;;AAE3B,MAAM,iBAAiB;AAEvB,SAAS,+BAA+B,SAAiD;CACvF,IAAI,qBAAqB,KAAK,OAAO,GAAG,OAAO;CAC/C,IAAI,mBAAmB,KAAK,OAAO,GAAG,OAAO;CAC7C,IAAI,eAAe,KAAK,OAAO,GAAG,OAAO;CAEzC,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,kCACd,SACA,eACqC;CACrC,IAAI,CAAC,iBAAiB,kBAAkB,OAAO,OAAO,KAAA;CACtD,MAAM,aAAa,+BAA+B,OAAO;CACzD,IAAI,eAAe,QAAQ,OAAO,KAAA;CAClC,MAAM,QAAQ;CAEd,OAAO;EACL,sBAAsB;EACtB,iBAAiB,OAAO,EAAE,aAAa;GACrC,MAAM,mBAAoB,OAAO,iBAAiB,aAAa,CAAC;GAEhE,IAAI,iBAAiB,aAAa,KAAA,KAAa,iBAAiB,WAAW,KAAA,GACzE,OAAO;GAIT,OAAO,OAAO;GACd,OAAO,OAAO;GACd,OAAO,OAAO;GAEd,OAAO,kBAAkB;IACvB,GAAG,OAAO;IACV,WAAW;KACT,GAAG;KACH,GAAI,eAAe,aACf;MAAE,UAAU;OAAE,MAAM;OAAY,SAAS;MAAa;MAAG,QAAQ,mBAAmB,SAAS,KAAK;KAAE,IACpG,EAAE,UAAU;MAAE,MAAM;MAAW,cAAc,iCAAiC;KAAO,EAAE;IAC7F;GACF;GAEA,OAAO;EACT;CACF;AACF;;;;;;AAOA,SAAgB,yBAAyB,OAA0C,CAAC,GAAiB;CACnG,QAAQ,OAAO,KAA6B,SAAuC;EACjF,MAAM,UAAU,KAAK,eAAe,eAAe;EACnD,QAAQ,OAAO;EAGf,IADmB,QAAQ,IAAI,WAClB,CAAC,EAAE,SAAS,WACvB,MAAM,IAAI,MAAM,oEAAoE;EAGtF,MAAM,cAAc,MAAM,QAAQ,UAAU,WAAW;EACvD,IAAI,CAAC,aACH,MAAM,IAAI,0BAA0B,6BAA6B;EAInE,MAAM,UAAU,IAAI,QAAQ,eAAe,UAAU,IAAI,UAAU,KAAA,CAAS;EAC5E,IAAI,MAAM,SAAS,IAAI,QAAQ,KAAK,OAAO,CAAC,CAAC,SAAS,OAAO,QAAQ,QAAQ,IAAI,KAAK,KAAK,CAAC;EAC5F,QAAQ,OAAO,eAAe;EAC9B,QAAQ,OAAO,WAAW;EAC1B,QAAQ,IAAI,iBAAiB,UAAU,aAAa;EACpD,MAAM,gBAAgB,QAAQ,IAAI,gBAAgB,KAAK,GAAA,CACpD,MAAM,GAAG,CAAC,CACV,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,CACxB,OAAO,OAAO;EACjB,QAAQ,IAAI,kBAAkB,MAAM,qBAAK,IAAI,IAAI,CAAC,GAAG,sBAAsB,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;EACvG,QAAQ,IAAI,qBAAqB,YAAY;EAE7C,IAAI;GACF,OAAO,MAAM,MAAM,KAAK;IAAE,GAAG;IAAM;GAAQ,CAAC;EAC9C,SAAS,OAAO;GACd,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,OAAO,OAAkC,EAC9C,YAAY,eAAe,MAAM,IAAI,SAAS,IAAI,OAAO,QAAQ,WAAW,MAAM,IAAI,IACxF,CAAC;GAEH,MAAM;EACR;CACF;AACF;;;;;AAMA,SAAgB,0BACd,UAAkB,4BAClB,SACmB;CACnB,MAAM,UAAU,SAAS;CACzB,MAAM,qBAAqB,kCAAkC,SAAS,SAAS,aAAa;CAC5F,MAAM,aAAa;EAAC;EAAsB;EAAuB,GAAI,qBAAqB,CAAC,kBAAkB,IAAI,CAAC;CAAE;CAGpH,IAAI,QAAQ,IAAI,aAAa,UAAU,QAAQ,IAAI,QAKjD,OAAO,kBAAkB;EACvB,OALgB,gBAAgB;GAChC,QAAQ;GACR;EACF,CAEiB,CAAC,CAAC,OAAO;EACxB;CACF,CAAC;CAUH,OAAO,kBAAkB;EACvB,OARgB,gBAAgB;GAChC,QAAQ;GACR;GACA,OAAO,yBAAyB,EAAE,aAAa,SAAS,YAAY,CAAC;EACvE,CAIiB,CAAC,CAAC,OAAO;EACxB;CACF,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"claude-max.js","names":[],"sources":["../../src/providers/claude-max.ts"],"sourcesContent":["/**\n * Claude Max OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with Claude Max plan.\n * The OAuth endpoint requires a specific system message to be present.\n */\n\nimport { createAnthropic } from '@ai-sdk/anthropic';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport { wrapLanguageModel } from 'ai';\nimport type { LanguageModelMiddleware } from 'ai';\nimport { ProviderAuthRequiredError } from '../auth/provider-auth-error.js';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\nimport { ANTHROPIC_PROMPT_CACHE_TTL } from './anthropic-prompt-cache.js';\nimport type { AnthropicPromptCacheScope } from './anthropic-prompt-cache.js';\nimport type { ThinkingLevel } from './openai-codex.js';\n\n// Required for Claude Max plan OAuth - the endpoint checks for this system message\nconst claudeCodeIdentity = \"You are Claude Code, Anthropic's official CLI for Claude.\";\n\n// Betas required for Claude Max plan OAuth. Merged with (not replacing) any\n// betas the AI SDK already set on the request — e.g. the SDK adds\n// `server-side-fallback-2026-06-01` when `providerOptions.anthropic.fallbacks`\n// is configured; dropping it makes the API reject the `fallbacks` body field\n// with \"Extra inputs are not permitted\".\nconst OAUTH_REQUIRED_BETAS = [\n 'oauth-2025-04-20',\n 'claude-code-20250219',\n 'interleaved-thinking-2025-05-14',\n 'fine-grained-tool-streaming-2025-05-14',\n];\n\n// Singleton auth storage instance\nlet authStorageInstance: AuthStorage | null = null;\n\n/**\n * Get or create the shared AuthStorage instance\n */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/**\n * Set a custom AuthStorage instance (useful for TUI integration)\n */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n/**\n * Middleware that injects the Claude Code identity system message\n * Required for Claude Max OAuth authentication\n */\nexport const claudeCodeMiddleware: LanguageModelMiddleware = {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n // Prepend the Claude Code identity as the first system message\n const systemMessage = {\n role: 'system' as const,\n content: claudeCodeIdentity,\n };\n\n if (params.temperature) {\n delete params.topP;\n }\n\n return {\n ...params,\n prompt: [systemMessage, ...params.prompt],\n };\n },\n};\n\n/**\n * Prompt caching middleware for Anthropic\n *\n * Adds cache breakpoints at strategic locations:\n * 1. Last system message (end of static instructions + dynamic memory)\n * 2. Most recent user/assistant message (conversation context), `conversation` scope only\n *\n * This allows Anthropic to cache:\n * - System prompts and instructions (rarely change)\n * - Conversation history up to the last message\n */\nexport const createPromptCacheMiddleware = (scope: AnthropicPromptCacheScope): LanguageModelMiddleware => ({\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n const prompt = [...params.prompt];\n\n const cacheControl = {\n type: 'ephemeral' as const,\n ttl: scope === 'conversation' ? ANTHROPIC_PROMPT_CACHE_TTL : ('5m' as const),\n };\n\n // Helper to add cache control to a message's last content part\n const addCacheToMessage = (msg: any) => {\n // For system messages with string content\n if (typeof msg.content === 'string') {\n return {\n ...msg,\n providerOptions: {\n ...msg.providerOptions,\n anthropic: { ...msg.providerOptions?.anthropic, cacheControl },\n },\n };\n }\n\n // For messages with array content, add to last part\n if (Array.isArray(msg.content) && msg.content.length > 0) {\n const content = [...msg.content];\n const lastPart = content[content.length - 1];\n content[content.length - 1] = {\n ...lastPart,\n providerOptions: {\n ...lastPart.providerOptions,\n anthropic: { ...lastPart.providerOptions?.anthropic, cacheControl },\n },\n };\n return { ...msg, content };\n }\n\n return msg;\n };\n\n // Find the last system message index\n let lastSystemIdx = -1;\n for (let i = prompt.length - 1; i >= 0; i--) {\n if ((prompt[i] as any).role === 'system') {\n lastSystemIdx = i;\n break;\n }\n }\n\n // Add cache breakpoint to last system message\n if (lastSystemIdx >= 0) {\n prompt[lastSystemIdx] = addCacheToMessage(prompt[lastSystemIdx]);\n }\n\n // Add cache breakpoint to the most recent message (last in array)\n const lastIdx = prompt.length - 1;\n if (scope === 'conversation' && lastIdx >= 0 && lastIdx !== lastSystemIdx) {\n prompt[lastIdx] = addCacheToMessage(prompt[lastIdx]);\n }\n\n return { ...params, prompt };\n },\n});\n\ntype ActiveThinkingLevel = Exclude<ThinkingLevel, 'off'>;\n\n// Anthropic's effort scale matches mastracode thinking levels 1:1 (minus 'off'),\n// including 'max' — the level OpenAI's scale stops short of.\nconst ANTHROPIC_EFFORT: Record<ActiveThinkingLevel, 'low' | 'medium' | 'high' | 'xhigh' | 'max'> = {\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'xhigh',\n max: 'max',\n};\n\nconst ANTHROPIC_XHIGH_EFFORT_RE = /claude-(?:opus-4-[78]|opus-5|sonnet-5|fable-5)/;\n\nfunction getAnthropicEffort(modelId: string, level: ActiveThinkingLevel) {\n if (level === 'xhigh' && !ANTHROPIC_XHIGH_EFFORT_RE.test(modelId)) return 'high';\n return ANTHROPIC_EFFORT[level];\n}\n\n// Extended-thinking budgets for models that predate adaptive thinking/effort.\n// Budgets count toward max_tokens, so they stay well below the smallest\n// output ceiling of the budget-era models (32k on Opus 4.0/4.1).\nconst ANTHROPIC_THINKING_BUDGET_TOKENS: Record<ActiveThinkingLevel, number> = {\n low: 4096,\n medium: 8192,\n high: 16384,\n xhigh: 24576,\n max: 24576,\n};\n\n/** Claude generations that support adaptive thinking + `output_config.effort`. */\nconst ADAPTIVE_THINKING_RE = /claude-(?:sonnet-4-6|opus-4-[678]|opus-5|sonnet-5|fable-5)/;\n/** Older generations that support extended thinking via `budget_tokens`. */\nconst BUDGET_THINKING_RE = /claude-(?:3-7|sonnet-4|opus-4|haiku-4-5)/;\n/** Generations with no extended-thinking support at all. */\nconst NO_THINKING_RE = /claude-(?:instant|v?2(?:[-.:]|$)|3(?:[-.]|$)|3-5)/;\n\nfunction getAnthropicThinkingCapability(modelId: string): 'adaptive' | 'budget' | 'none' {\n if (ADAPTIVE_THINKING_RE.test(modelId)) return 'adaptive';\n if (BUDGET_THINKING_RE.test(modelId)) return 'budget';\n if (NO_THINKING_RE.test(modelId)) return 'none';\n // Unknown (i.e. newer) Claude models: assume the current API surface.\n return 'adaptive';\n}\n\n/**\n * Middleware that maps the session thinking level onto Anthropic extended\n * thinking. Returns `undefined` (no middleware) when the level is `off` or the\n * model doesn't support thinking, preserving the previous request shape.\n *\n * - Adaptive-era models (Sonnet 4.6+/Opus 4.6+): `thinking: adaptive` plus\n * `output_config.effort` mapped 1:1 from the level (including `max`).\n * - Budget-era models (Claude 3.7 – Opus 4.5): `thinking: enabled` with a\n * `budget_tokens` value derived from the level.\n */\nexport function createAnthropicThinkingMiddleware(\n modelId: string,\n thinkingLevel?: ThinkingLevel,\n): LanguageModelMiddleware | undefined {\n if (!thinkingLevel || thinkingLevel === 'off') return undefined;\n const capability = getAnthropicThinkingCapability(modelId);\n if (capability === 'none') return undefined;\n const level = thinkingLevel as ActiveThinkingLevel;\n\n return {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n const anthropicOptions = (params.providerOptions?.anthropic ?? {}) as Record<string, unknown>;\n // Don't override explicit per-request thinking configuration.\n if (anthropicOptions.thinking !== undefined || anthropicOptions.effort !== undefined) {\n return params;\n }\n\n // Anthropic rejects sampling parameters when thinking is enabled.\n delete params.temperature;\n delete params.topP;\n delete params.topK;\n\n params.providerOptions = {\n ...params.providerOptions,\n anthropic: {\n ...anthropicOptions,\n ...(capability === 'adaptive'\n ? { thinking: { type: 'adaptive', display: 'summarized' }, effort: getAnthropicEffort(modelId, level) }\n : { thinking: { type: 'enabled', budgetTokens: ANTHROPIC_THINKING_BUDGET_TOKENS[level] } }),\n },\n } as typeof params.providerOptions;\n\n return params;\n },\n };\n}\n\n/**\n * Build a fetch function that handles Anthropic OAuth.\n * Preserves non-auth headers from init (critical for gateway auth header to survive\n * when used with the gateway). Strips `authorization` and `x-api-key`.\n */\nexport function buildAnthropicOAuthFetch(opts: { authStorage?: CredentialStore } = {}): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const storedCred = storage.get('anthropic');\n if (storedCred?.type === 'api_key') {\n throw new Error('Anthropic API key credential is configured, but OAuth is required.');\n }\n\n const accessToken = await storage.getApiKey('anthropic');\n if (!accessToken) {\n throw new ProviderAuthRequiredError('Not logged in to Anthropic.');\n }\n\n // Preserve Request headers and let explicit init headers override them.\n const headers = new Headers(url instanceof Request ? url.headers : undefined);\n if (init?.headers) new Headers(init.headers).forEach((value, key) => headers.set(key, value));\n headers.delete('authorization');\n headers.delete('x-api-key');\n headers.set('Authorization', `Bearer ${accessToken}`);\n const requestBetas = (headers.get('anthropic-beta') ?? '')\n .split(',')\n .map(beta => beta.trim())\n .filter(Boolean);\n headers.set('anthropic-beta', Array.from(new Set([...OAUTH_REQUIRED_BETAS, ...requestBetas])).join(','));\n headers.set('anthropic-version', '2023-06-01');\n\n try {\n return await fetch(url, { ...init, headers });\n } catch (error) {\n if (error && typeof error === 'object') {\n Object.assign(error as Record<string, unknown>, {\n requestUrl: url instanceof URL ? url.toString() : typeof url === 'string' ? url : url.url,\n });\n }\n throw error;\n }\n }) as typeof fetch;\n}\n\n/**\n * Creates an Anthropic model using Claude Max OAuth authentication\n * Uses OAuth tokens from AuthStorage (auto-refreshes when needed)\n */\nexport function opencodeClaudeMaxProvider(\n modelId: string = 'claude-sonnet-4-20250514',\n options?: {\n headers?: Record<string, string>;\n authStorage?: CredentialStore;\n thinkingLevel?: ThinkingLevel;\n promptCacheScope?: AnthropicPromptCacheScope;\n },\n): MastraModelConfig {\n const headers = options?.headers;\n const thinkingMiddleware = createAnthropicThinkingMiddleware(modelId, options?.thinkingLevel);\n const middleware = [\n claudeCodeMiddleware,\n createPromptCacheMiddleware(options?.promptCacheScope ?? 'conversation'),\n ...(thinkingMiddleware ? [thinkingMiddleware] : []),\n ];\n\n // Test environment: use API key\n if (process.env.NODE_ENV === 'test' || process.env.VITEST) {\n const anthropic = createAnthropic({\n apiKey: 'test-api-key',\n headers,\n });\n return wrapLanguageModel({\n model: anthropic(modelId),\n middleware,\n });\n }\n\n const anthropic = createAnthropic({\n apiKey: 'oauth-placeholder',\n headers,\n fetch: buildAnthropicOAuthFetch({ authStorage: options?.authStorage }) as any,\n });\n\n // Wrap with middleware to inject Claude Code identity and enable prompt caching\n return wrapLanguageModel({\n model: anthropic(modelId),\n middleware,\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { LeaseProvider } from '@mastra/core/events';
|
|
2
|
+
import type { Mastra } from '@mastra/core/mastra';
|
|
3
|
+
/**
|
|
4
|
+
* Whether this call runs inside this process's own dispatch pass for
|
|
5
|
+
* `resourceId`, as opposed to the dispatch schedule every process sharing the
|
|
6
|
+
* database takes turns running.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isDispatchingNotificationsFor(resourceId: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Whether a delivery-time policy should leave a due notification pending
|
|
11
|
+
* (`hold`). Deliveries happen from this process's own dispatch pass for the
|
|
12
|
+
* record's resource. Anything else holds: a thread no session here owns
|
|
13
|
+
* belongs to another process sharing the database, and once this process's
|
|
14
|
+
* dispatcher runs, its own resources are delivered by whichever process holds
|
|
15
|
+
* their dispatch lease, never by the shared dispatch schedule.
|
|
16
|
+
*/
|
|
17
|
+
export declare function shouldHoldNotificationDelivery({ resourceId, ownedHere, dispatcher, }: {
|
|
18
|
+
resourceId: string;
|
|
19
|
+
/** Whether a session in this process owns the resource. */
|
|
20
|
+
ownedHere: boolean;
|
|
21
|
+
dispatcher: {
|
|
22
|
+
readonly running: boolean;
|
|
23
|
+
};
|
|
24
|
+
}): boolean;
|
|
25
|
+
export type ResourceNotificationDispatcherOptions = {
|
|
26
|
+
getMastra: () => Mastra | undefined;
|
|
27
|
+
/** The resources this process runs sessions for. */
|
|
28
|
+
getResourceIds: () => Iterable<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Cross-process leases. When set, a resource's notifications are dispatched
|
|
31
|
+
* only by the process holding its dispatch lease, so two processes serving
|
|
32
|
+
* the same resource (worktrees of one repo) never both deliver them.
|
|
33
|
+
*/
|
|
34
|
+
leases?: LeaseProvider;
|
|
35
|
+
/** Unique to this process. */
|
|
36
|
+
owner: string;
|
|
37
|
+
intervalMs?: number;
|
|
38
|
+
leaseTtlMs?: number;
|
|
39
|
+
onError?: (error: unknown) => void;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Delivers the due notifications of the resources this process serves, on a
|
|
43
|
+
* timer of its own.
|
|
44
|
+
*
|
|
45
|
+
* Every Mastra Code process shares one database, so one notification store and
|
|
46
|
+
* one dispatch schedule, and the scheduler gives each fire to whichever process
|
|
47
|
+
* claims it first, usually the same one for as long as it runs. That process
|
|
48
|
+
* cannot run another project's thread, so its delivery policy holds those
|
|
49
|
+
* records. Each process therefore dispatches its own resources here.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createResourceNotificationDispatcher(options: ResourceNotificationDispatcherOptions): {
|
|
52
|
+
readonly running: boolean;
|
|
53
|
+
start(): void;
|
|
54
|
+
stop(): Promise<void>;
|
|
55
|
+
/** Runs one dispatch pass now. Only dispatches while started. */
|
|
56
|
+
tick: () => Promise<void>;
|
|
57
|
+
};
|
|
58
|
+
export type ResourceNotificationDispatcher = ReturnType<typeof createResourceNotificationDispatcher>;
|
|
59
|
+
//# sourceMappingURL=notification-dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-dispatch.d.ts","sourceRoot":"","sources":["../../src/utils/notification-dispatch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAWlD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAEzE;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,EAC7C,UAAU,EACV,SAAS,EACT,UAAU,GACX,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3C,GAAG,OAAO,CAGV;AAED,MAAM,MAAM,qCAAqC,GAAG;IAClD,SAAS,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACpC,oDAAoD;IACpD,cAAc,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;IACvC;;;;OAIG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,oCAAoC,CAAC,OAAO,EAAE,qCAAqC;sBA2DhF,OAAO;aAGb,IAAI;YAMC,OAAO,CAAC,IAAI,CAAC;IAM3B,iEAAiE;gBA1BlD,OAAO,CAAC,IAAI,CAAC;EA6B/B;AAED,MAAM,MAAM,8BAA8B,GAAG,UAAU,CAAC,OAAO,oCAAoC,CAAC,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { notificationDispatchLeaseKey } from "./signals-pubsub.js";
|
|
2
|
+
import { dispatchDueNotifications } from "@mastra/core/notifications";
|
|
3
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
4
|
+
//#region src/utils/notification-dispatch.ts
|
|
5
|
+
const DEFAULT_INTERVAL_MS = 1e4;
|
|
6
|
+
const DEFAULT_LEASE_TTL_MS = 6e4;
|
|
7
|
+
/** The resource whose notifications the current async context is dispatching. */
|
|
8
|
+
const dispatchingFor = new AsyncLocalStorage();
|
|
9
|
+
/**
|
|
10
|
+
* Whether this call runs inside this process's own dispatch pass for
|
|
11
|
+
* `resourceId`, as opposed to the dispatch schedule every process sharing the
|
|
12
|
+
* database takes turns running.
|
|
13
|
+
*/
|
|
14
|
+
function isDispatchingNotificationsFor(resourceId) {
|
|
15
|
+
return dispatchingFor.getStore() === resourceId;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Whether a delivery-time policy should leave a due notification pending
|
|
19
|
+
* (`hold`). Deliveries happen from this process's own dispatch pass for the
|
|
20
|
+
* record's resource. Anything else holds: a thread no session here owns
|
|
21
|
+
* belongs to another process sharing the database, and once this process's
|
|
22
|
+
* dispatcher runs, its own resources are delivered by whichever process holds
|
|
23
|
+
* their dispatch lease, never by the shared dispatch schedule.
|
|
24
|
+
*/
|
|
25
|
+
function shouldHoldNotificationDelivery({ resourceId, ownedHere, dispatcher }) {
|
|
26
|
+
if (isDispatchingNotificationsFor(resourceId)) return false;
|
|
27
|
+
return !ownedHere || dispatcher.running;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Delivers the due notifications of the resources this process serves, on a
|
|
31
|
+
* timer of its own.
|
|
32
|
+
*
|
|
33
|
+
* Every Mastra Code process shares one database, so one notification store and
|
|
34
|
+
* one dispatch schedule, and the scheduler gives each fire to whichever process
|
|
35
|
+
* claims it first, usually the same one for as long as it runs. That process
|
|
36
|
+
* cannot run another project's thread, so its delivery policy holds those
|
|
37
|
+
* records. Each process therefore dispatches its own resources here.
|
|
38
|
+
*/
|
|
39
|
+
function createResourceNotificationDispatcher(options) {
|
|
40
|
+
const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
41
|
+
const leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
42
|
+
const held = /* @__PURE__ */ new Set();
|
|
43
|
+
let timer;
|
|
44
|
+
let ticking;
|
|
45
|
+
const release = async (resourceId) => {
|
|
46
|
+
held.delete(resourceId);
|
|
47
|
+
await options.leases?.releaseLease(notificationDispatchLeaseKey(resourceId), options.owner).catch(options.onError);
|
|
48
|
+
};
|
|
49
|
+
const claim = async (resourceId) => {
|
|
50
|
+
if (!options.leases) return true;
|
|
51
|
+
try {
|
|
52
|
+
const { acquired } = await options.leases.acquireLease(notificationDispatchLeaseKey(resourceId), options.owner, leaseTtlMs);
|
|
53
|
+
if (acquired) held.add(resourceId);
|
|
54
|
+
else held.delete(resourceId);
|
|
55
|
+
return acquired;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
options.onError?.(error);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const runTick = async () => {
|
|
62
|
+
const mastra = options.getMastra();
|
|
63
|
+
const storage = await mastra?.getStorage()?.getStore("notifications");
|
|
64
|
+
if (!mastra || !storage) return;
|
|
65
|
+
const resourceIds = new Set(options.getResourceIds());
|
|
66
|
+
for (const resourceId of [...held]) if (!resourceIds.has(resourceId)) await release(resourceId);
|
|
67
|
+
for (const resourceId of resourceIds) {
|
|
68
|
+
if (!timer || !await claim(resourceId)) continue;
|
|
69
|
+
try {
|
|
70
|
+
await dispatchingFor.run(resourceId, () => dispatchDueNotifications({
|
|
71
|
+
mastra,
|
|
72
|
+
storage,
|
|
73
|
+
resourceId
|
|
74
|
+
}));
|
|
75
|
+
} catch (error) {
|
|
76
|
+
options.onError?.(error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const tick = () => {
|
|
81
|
+
ticking ??= runTick().catch((error) => options.onError?.(error)).finally(() => {
|
|
82
|
+
ticking = void 0;
|
|
83
|
+
});
|
|
84
|
+
return ticking;
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
get running() {
|
|
88
|
+
return timer !== void 0;
|
|
89
|
+
},
|
|
90
|
+
start() {
|
|
91
|
+
if (timer) return;
|
|
92
|
+
timer = setInterval(() => void tick(), intervalMs);
|
|
93
|
+
timer.unref?.();
|
|
94
|
+
tick();
|
|
95
|
+
},
|
|
96
|
+
async stop() {
|
|
97
|
+
if (timer) clearInterval(timer);
|
|
98
|
+
timer = void 0;
|
|
99
|
+
await ticking;
|
|
100
|
+
await Promise.all([...held].map(release));
|
|
101
|
+
},
|
|
102
|
+
/** Runs one dispatch pass now. Only dispatches while started. */
|
|
103
|
+
tick
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { createResourceNotificationDispatcher, isDispatchingNotificationsFor, shouldHoldNotificationDelivery };
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=notification-dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-dispatch.js","names":[],"sources":["../../src/utils/notification-dispatch.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport type { LeaseProvider } from '@mastra/core/events';\nimport type { Mastra } from '@mastra/core/mastra';\nimport { dispatchDueNotifications } from '@mastra/core/notifications';\n\nimport { notificationDispatchLeaseKey } from './signals-pubsub.js';\n\nconst DEFAULT_INTERVAL_MS = 10_000;\nconst DEFAULT_LEASE_TTL_MS = 60_000;\n\n/** The resource whose notifications the current async context is dispatching. */\nconst dispatchingFor = new AsyncLocalStorage<string>();\n\n/**\n * Whether this call runs inside this process's own dispatch pass for\n * `resourceId`, as opposed to the dispatch schedule every process sharing the\n * database takes turns running.\n */\nexport function isDispatchingNotificationsFor(resourceId: string): boolean {\n return dispatchingFor.getStore() === resourceId;\n}\n\n/**\n * Whether a delivery-time policy should leave a due notification pending\n * (`hold`). Deliveries happen from this process's own dispatch pass for the\n * record's resource. Anything else holds: a thread no session here owns\n * belongs to another process sharing the database, and once this process's\n * dispatcher runs, its own resources are delivered by whichever process holds\n * their dispatch lease, never by the shared dispatch schedule.\n */\nexport function shouldHoldNotificationDelivery({\n resourceId,\n ownedHere,\n dispatcher,\n}: {\n resourceId: string;\n /** Whether a session in this process owns the resource. */\n ownedHere: boolean;\n dispatcher: { readonly running: boolean };\n}): boolean {\n if (isDispatchingNotificationsFor(resourceId)) return false;\n return !ownedHere || dispatcher.running;\n}\n\nexport type ResourceNotificationDispatcherOptions = {\n getMastra: () => Mastra | undefined;\n /** The resources this process runs sessions for. */\n getResourceIds: () => Iterable<string>;\n /**\n * Cross-process leases. When set, a resource's notifications are dispatched\n * only by the process holding its dispatch lease, so two processes serving\n * the same resource (worktrees of one repo) never both deliver them.\n */\n leases?: LeaseProvider;\n /** Unique to this process. */\n owner: string;\n intervalMs?: number;\n leaseTtlMs?: number;\n onError?: (error: unknown) => void;\n};\n\n/**\n * Delivers the due notifications of the resources this process serves, on a\n * timer of its own.\n *\n * Every Mastra Code process shares one database, so one notification store and\n * one dispatch schedule, and the scheduler gives each fire to whichever process\n * claims it first, usually the same one for as long as it runs. That process\n * cannot run another project's thread, so its delivery policy holds those\n * records. Each process therefore dispatches its own resources here.\n */\nexport function createResourceNotificationDispatcher(options: ResourceNotificationDispatcherOptions) {\n const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;\n const leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;\n const held = new Set<string>();\n let timer: ReturnType<typeof setInterval> | undefined;\n let ticking: Promise<void> | undefined;\n\n const release = async (resourceId: string) => {\n held.delete(resourceId);\n await options.leases?.releaseLease(notificationDispatchLeaseKey(resourceId), options.owner).catch(options.onError);\n };\n\n const claim = async (resourceId: string): Promise<boolean> => {\n if (!options.leases) return true;\n try {\n // Acquiring a lease this process already holds renews it.\n const { acquired } = await options.leases.acquireLease(\n notificationDispatchLeaseKey(resourceId),\n options.owner,\n leaseTtlMs,\n );\n if (acquired) held.add(resourceId);\n else held.delete(resourceId);\n return acquired;\n } catch (error) {\n options.onError?.(error);\n return false;\n }\n };\n\n const runTick = async () => {\n const mastra = options.getMastra();\n const storage = await mastra?.getStorage()?.getStore('notifications');\n if (!mastra || !storage) return;\n const resourceIds = new Set(options.getResourceIds());\n for (const resourceId of [...held]) {\n if (!resourceIds.has(resourceId)) await release(resourceId);\n }\n for (const resourceId of resourceIds) {\n if (!timer || !(await claim(resourceId))) continue;\n try {\n await dispatchingFor.run(resourceId, () => dispatchDueNotifications({ mastra, storage, resourceId }));\n } catch (error) {\n options.onError?.(error);\n }\n }\n };\n\n const tick = (): Promise<void> => {\n // Ticks run from a timer, so a failure must be reported, never left as an unhandled rejection.\n ticking ??= runTick()\n .catch(error => options.onError?.(error))\n .finally(() => {\n ticking = undefined;\n });\n return ticking;\n };\n\n return {\n get running(): boolean {\n return timer !== undefined;\n },\n start(): void {\n if (timer) return;\n timer = setInterval(() => void tick(), intervalMs);\n timer.unref?.();\n void tick();\n },\n async stop(): Promise<void> {\n if (timer) clearInterval(timer);\n timer = undefined;\n await ticking;\n await Promise.all([...held].map(release));\n },\n /** Runs one dispatch pass now. Only dispatches while started. */\n tick,\n };\n}\n\nexport type ResourceNotificationDispatcher = ReturnType<typeof createResourceNotificationDispatcher>;\n"],"mappings":";;;;AAQA,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;;AAG7B,MAAM,iBAAiB,IAAI,kBAA0B;;;;;;AAOrD,SAAgB,8BAA8B,YAA6B;CACzE,OAAO,eAAe,SAAS,MAAM;AACvC;;;;;;;;;AAUA,SAAgB,+BAA+B,EAC7C,YACA,WACA,cAMU;CACV,IAAI,8BAA8B,UAAU,GAAG,OAAO;CACtD,OAAO,CAAC,aAAa,WAAW;AAClC;;;;;;;;;;;AA6BA,SAAgB,qCAAqC,SAAgD;CACnG,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,uBAAO,IAAI,IAAY;CAC7B,IAAI;CACJ,IAAI;CAEJ,MAAM,UAAU,OAAO,eAAuB;EAC5C,KAAK,OAAO,UAAU;EACtB,MAAM,QAAQ,QAAQ,aAAa,6BAA6B,UAAU,GAAG,QAAQ,KAAK,CAAC,CAAC,MAAM,QAAQ,OAAO;CACnH;CAEA,MAAM,QAAQ,OAAO,eAAyC;EAC5D,IAAI,CAAC,QAAQ,QAAQ,OAAO;EAC5B,IAAI;GAEF,MAAM,EAAE,aAAa,MAAM,QAAQ,OAAO,aACxC,6BAA6B,UAAU,GACvC,QAAQ,OACR,UACF;GACA,IAAI,UAAU,KAAK,IAAI,UAAU;QAC5B,KAAK,OAAO,UAAU;GAC3B,OAAO;EACT,SAAS,OAAO;GACd,QAAQ,UAAU,KAAK;GACvB,OAAO;EACT;CACF;CAEA,MAAM,UAAU,YAAY;EAC1B,MAAM,SAAS,QAAQ,UAAU;EACjC,MAAM,UAAU,MAAM,QAAQ,WAAW,CAAC,EAAE,SAAS,eAAe;EACpE,IAAI,CAAC,UAAU,CAAC,SAAS;EACzB,MAAM,cAAc,IAAI,IAAI,QAAQ,eAAe,CAAC;EACpD,KAAK,MAAM,cAAc,CAAC,GAAG,IAAI,GAC/B,IAAI,CAAC,YAAY,IAAI,UAAU,GAAG,MAAM,QAAQ,UAAU;EAE5D,KAAK,MAAM,cAAc,aAAa;GACpC,IAAI,CAAC,SAAS,CAAE,MAAM,MAAM,UAAU,GAAI;GAC1C,IAAI;IACF,MAAM,eAAe,IAAI,kBAAkB,yBAAyB;KAAE;KAAQ;KAAS;IAAW,CAAC,CAAC;GACtG,SAAS,OAAO;IACd,QAAQ,UAAU,KAAK;GACzB;EACF;CACF;CAEA,MAAM,aAA4B;EAEhC,YAAY,QAAQ,CAAC,CAClB,OAAM,UAAS,QAAQ,UAAU,KAAK,CAAC,CAAC,CACxC,cAAc;GACb,UAAU,KAAA;EACZ,CAAC;EACH,OAAO;CACT;CAEA,OAAO;EACL,IAAI,UAAmB;GACrB,OAAO,UAAU,KAAA;EACnB;EACA,QAAc;GACZ,IAAI,OAAO;GACX,QAAQ,kBAAkB,KAAK,KAAK,GAAG,UAAU;GACjD,MAAM,QAAQ;GACd,KAAU;EACZ;EACA,MAAM,OAAsB;GAC1B,IAAI,OAAO,cAAc,KAAK;GAC9B,QAAQ,KAAA;GACR,MAAM;GACN,MAAM,QAAQ,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC;EAC1C;;EAEA;CACF;AACF"}
|
|
@@ -1,25 +1,52 @@
|
|
|
1
1
|
import { PubSub, UnixSocketPubSub } from '@mastra/core/events';
|
|
2
2
|
import type { Event, EventCallback, LeaseProvider, PubSubDeliveryMode, SubscribeOptions } from '@mastra/core/events';
|
|
3
|
+
export type SignalsPubSubOptions = {
|
|
4
|
+
/** Directory that holds every resource's socket directory. Defaults to `/tmp/mc`. */
|
|
5
|
+
rootDir?: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* The lease that lets exactly one process dispatch a resource's due
|
|
9
|
+
* notifications. Held in that resource's directory, so every process serving
|
|
10
|
+
* the resource contends for it whichever project it was started in.
|
|
11
|
+
*/
|
|
12
|
+
export declare function notificationDispatchLeaseKey(resourceId: string): string;
|
|
3
13
|
/**
|
|
4
14
|
* A PubSub that manages one Unix socket per topic for cross-process signal
|
|
5
|
-
* coordination
|
|
15
|
+
* coordination between mastracode processes on this machine.
|
|
16
|
+
*
|
|
17
|
+
* Socket paths use `<root>/<dir>/<sanitized-topic>.sock` (root `/tmp/mc`)
|
|
18
|
+
* for inspectability and automatic OS cleanup. Each topic gets its own
|
|
19
|
+
* socket, so broker election and message routing are per-topic.
|
|
20
|
+
*
|
|
21
|
+
* Every process shares one database, so any process can act on any project's
|
|
22
|
+
* thread — the notification dispatcher, for one, delivers whatever is due.
|
|
23
|
+
* Whatever coordinates a thread therefore lives with the thread, not with the
|
|
24
|
+
* process touching it:
|
|
6
25
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
26
|
+
* - A thread's stream topic and its leases (the run lease and the claim
|
|
27
|
+
* lease) live in the directory of the resource that owns the thread, so a
|
|
28
|
+
* process from another project sees that thread's active run and claim.
|
|
29
|
+
* - Thread-owner discovery lives in `<root>/_shared/`, so a process that finds
|
|
30
|
+
* a thread claimed can reach the owner and hand it the signal. It only
|
|
31
|
+
* answers for a thread the asker names, so it discloses nothing new.
|
|
32
|
+
* - Everything else, including peer discovery (which lists threads), stays in
|
|
33
|
+
* this process's own resource directory.
|
|
11
34
|
*
|
|
12
35
|
* Stale sockets from crashed processes are handled by
|
|
13
36
|
* {@link UnixSocketPubSub}'s built-in election logic: it detects
|
|
14
37
|
* ECONNREFUSED on a dead broker socket, unlinks it, and re-elects.
|
|
15
38
|
* No blanket cleanup is needed here — that would break concurrent
|
|
16
|
-
* mc instances sharing
|
|
39
|
+
* mc instances sharing a directory.
|
|
17
40
|
*/
|
|
18
41
|
declare class SignalsPubSub extends PubSub {
|
|
19
42
|
#private;
|
|
20
|
-
constructor(resourceId: string);
|
|
43
|
+
constructor(resourceId: string, options?: SignalsPubSubOptions);
|
|
21
44
|
get supportedModes(): ReadonlyArray<PubSubDeliveryMode>;
|
|
22
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Filesystem leases. A thread's run and claim leases are held in the
|
|
47
|
+
* directory of the thread's resource, so every process contends for the
|
|
48
|
+
* same lease; any other key stays in this resource's directory.
|
|
49
|
+
*/
|
|
23
50
|
getLeaseProvider(): LeaseProvider;
|
|
24
51
|
publish(topic: string, event: Omit<Event, 'id' | 'createdAt'>, options?: {
|
|
25
52
|
localOnly?: boolean;
|
|
@@ -33,12 +60,14 @@ declare class SignalsPubSub extends PubSub {
|
|
|
33
60
|
}
|
|
34
61
|
/**
|
|
35
62
|
* Creates a per-topic PubSub backed by Unix sockets for cross-process signal
|
|
36
|
-
* and workflow event coordination
|
|
63
|
+
* and workflow event coordination between mastracode processes.
|
|
37
64
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* {@link
|
|
65
|
+
* Topics live under `/tmp/mc/<resourceId>/`, except that a thread's stream and
|
|
66
|
+
* leases live under its own resource's directory and thread-owner discovery
|
|
67
|
+
* under `/tmp/mc/_shared/` (see {@link SignalsPubSub}). Stale sockets from
|
|
68
|
+
* crashed processes are handled by the underlying {@link UnixSocketPubSub}'s
|
|
69
|
+
* broker election logic.
|
|
41
70
|
*/
|
|
42
|
-
export declare function createSignalsPubSub(resourceId: string): SignalsPubSub;
|
|
71
|
+
export declare function createSignalsPubSub(resourceId: string, options?: SignalsPubSubOptions): SignalsPubSub;
|
|
43
72
|
export {};
|
|
44
73
|
//# sourceMappingURL=signals-pubsub.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signals-pubsub.d.ts","sourceRoot":"","sources":["../../src/utils/signals-pubsub.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"signals-pubsub.d.ts","sourceRoot":"","sources":["../../src/utils/signals-pubsub.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAWrH,MAAM,MAAM,oBAAoB,GAAG;IACjC,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAiCF;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvE;AAiCD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,cAAM,aAAc,SAAQ,MAAM;;gBASpB,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB;IAgBlE,IAAa,cAAc,IAAI,aAAa,CAAC,kBAAkB,CAAC,CAE/D;IAED;;;;OAIG;IACH,gBAAgB,IAAI,aAAa;IAI3B,OAAO,CACX,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,WAAW,CAAC,EACtC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAChC,OAAO,CAAC,IAAI,CAAC;IAKV,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAU5B,sEAAsE;IACtE,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;CAqEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,aAAa,CAErG"}
|