@animalabs/membrane 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/context/index.d.ts +10 -0
  2. package/dist/context/index.d.ts.map +1 -0
  3. package/dist/context/index.js +9 -0
  4. package/dist/context/index.js.map +1 -0
  5. package/dist/context/process.d.ts +22 -0
  6. package/dist/context/process.d.ts.map +1 -0
  7. package/dist/context/process.js +369 -0
  8. package/dist/context/process.js.map +1 -0
  9. package/dist/context/types.d.ts +118 -0
  10. package/dist/context/types.d.ts.map +1 -0
  11. package/dist/context/types.js +60 -0
  12. package/dist/context/types.js.map +1 -0
  13. package/dist/index.d.ts +12 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +18 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/membrane.d.ts +96 -0
  18. package/dist/membrane.d.ts.map +1 -0
  19. package/dist/membrane.js +893 -0
  20. package/dist/membrane.js.map +1 -0
  21. package/dist/providers/anthropic.d.ts +36 -0
  22. package/dist/providers/anthropic.d.ts.map +1 -0
  23. package/dist/providers/anthropic.js +265 -0
  24. package/dist/providers/anthropic.js.map +1 -0
  25. package/dist/providers/index.d.ts +8 -0
  26. package/dist/providers/index.d.ts.map +1 -0
  27. package/dist/providers/index.js +8 -0
  28. package/dist/providers/index.js.map +1 -0
  29. package/dist/providers/openai-compatible.d.ts +74 -0
  30. package/dist/providers/openai-compatible.d.ts.map +1 -0
  31. package/dist/providers/openai-compatible.js +412 -0
  32. package/dist/providers/openai-compatible.js.map +1 -0
  33. package/dist/providers/openai.d.ts +69 -0
  34. package/dist/providers/openai.d.ts.map +1 -0
  35. package/dist/providers/openai.js +455 -0
  36. package/dist/providers/openai.js.map +1 -0
  37. package/dist/providers/openrouter.d.ts +76 -0
  38. package/dist/providers/openrouter.d.ts.map +1 -0
  39. package/dist/providers/openrouter.js +492 -0
  40. package/dist/providers/openrouter.js.map +1 -0
  41. package/dist/transforms/chat.d.ts +52 -0
  42. package/dist/transforms/chat.d.ts.map +1 -0
  43. package/dist/transforms/chat.js +136 -0
  44. package/dist/transforms/chat.js.map +1 -0
  45. package/dist/transforms/index.d.ts +6 -0
  46. package/dist/transforms/index.d.ts.map +1 -0
  47. package/dist/transforms/index.js +6 -0
  48. package/dist/transforms/index.js.map +1 -0
  49. package/dist/transforms/prefill.d.ts +89 -0
  50. package/dist/transforms/prefill.d.ts.map +1 -0
  51. package/dist/transforms/prefill.js +401 -0
  52. package/dist/transforms/prefill.js.map +1 -0
  53. package/dist/types/config.d.ts +103 -0
  54. package/dist/types/config.d.ts.map +1 -0
  55. package/dist/types/config.js +21 -0
  56. package/dist/types/config.js.map +1 -0
  57. package/dist/types/content.d.ts +81 -0
  58. package/dist/types/content.d.ts.map +1 -0
  59. package/dist/types/content.js +40 -0
  60. package/dist/types/content.js.map +1 -0
  61. package/dist/types/errors.d.ts +42 -0
  62. package/dist/types/errors.d.ts.map +1 -0
  63. package/dist/types/errors.js +208 -0
  64. package/dist/types/errors.js.map +1 -0
  65. package/dist/types/index.d.ts +18 -0
  66. package/dist/types/index.d.ts.map +1 -0
  67. package/dist/types/index.js +9 -0
  68. package/dist/types/index.js.map +1 -0
  69. package/dist/types/message.d.ts +46 -0
  70. package/dist/types/message.d.ts.map +1 -0
  71. package/dist/types/message.js +38 -0
  72. package/dist/types/message.js.map +1 -0
  73. package/dist/types/provider.d.ts +155 -0
  74. package/dist/types/provider.d.ts.map +1 -0
  75. package/dist/types/provider.js +5 -0
  76. package/dist/types/provider.js.map +1 -0
  77. package/dist/types/request.d.ts +78 -0
  78. package/dist/types/request.d.ts.map +1 -0
  79. package/dist/types/request.js +5 -0
  80. package/dist/types/request.js.map +1 -0
  81. package/dist/types/response.d.ts +131 -0
  82. package/dist/types/response.d.ts.map +1 -0
  83. package/dist/types/response.js +7 -0
  84. package/dist/types/response.js.map +1 -0
  85. package/dist/types/streaming.d.ts +164 -0
  86. package/dist/types/streaming.d.ts.map +1 -0
  87. package/dist/types/streaming.js +5 -0
  88. package/dist/types/streaming.js.map +1 -0
  89. package/dist/types/tools.d.ts +71 -0
  90. package/dist/types/tools.d.ts.map +1 -0
  91. package/dist/types/tools.js +5 -0
  92. package/dist/types/tools.js.map +1 -0
  93. package/dist/utils/index.d.ts +5 -0
  94. package/dist/utils/index.d.ts.map +1 -0
  95. package/dist/utils/index.js +5 -0
  96. package/dist/utils/index.js.map +1 -0
  97. package/dist/utils/stream-parser.d.ts +53 -0
  98. package/dist/utils/stream-parser.d.ts.map +1 -0
  99. package/dist/utils/stream-parser.js +359 -0
  100. package/dist/utils/stream-parser.js.map +1 -0
  101. package/dist/utils/tool-parser.d.ts +130 -0
  102. package/dist/utils/tool-parser.d.ts.map +1 -0
  103. package/dist/utils/tool-parser.js +571 -0
  104. package/dist/utils/tool-parser.js.map +1 -0
  105. package/package.json +37 -0
  106. package/src/context/index.ts +24 -0
  107. package/src/context/process.ts +520 -0
  108. package/src/context/types.ts +231 -0
  109. package/src/index.ts +23 -0
  110. package/src/membrane.ts +1174 -0
  111. package/src/providers/anthropic.ts +340 -0
  112. package/src/providers/index.ts +31 -0
  113. package/src/providers/openai-compatible.ts +570 -0
  114. package/src/providers/openai.ts +625 -0
  115. package/src/providers/openrouter.ts +662 -0
  116. package/src/transforms/chat.ts +212 -0
  117. package/src/transforms/index.ts +22 -0
  118. package/src/transforms/prefill.ts +585 -0
  119. package/src/types/config.ts +172 -0
  120. package/src/types/content.ts +181 -0
  121. package/src/types/errors.ts +277 -0
  122. package/src/types/index.ts +154 -0
  123. package/src/types/message.ts +89 -0
  124. package/src/types/provider.ts +249 -0
  125. package/src/types/request.ts +131 -0
  126. package/src/types/response.ts +223 -0
  127. package/src/types/streaming.ts +231 -0
  128. package/src/types/tools.ts +92 -0
  129. package/src/utils/index.ts +15 -0
  130. package/src/utils/stream-parser.ts +440 -0
  131. package/src/utils/tool-parser.ts +715 -0
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Chat mode transforms
3
+ *
4
+ * Converts normalized messages to role-based alternating format
5
+ * for providers that don't support prefill mode.
6
+ */
7
+ import { isTextContent, isToolUseContent, isToolResultContent } from '../types/index.js';
8
+ // ============================================================================
9
+ // Main Transform Function
10
+ // ============================================================================
11
+ /**
12
+ * Transform normalized request to chat format
13
+ */
14
+ export function transformToChat(request, options = {}) {
15
+ const { assistantName = 'Claude', userParticipants = [], mergeStrategy = 'concatenate', } = options;
16
+ const userSet = new Set(userParticipants);
17
+ const messages = [];
18
+ for (const message of request.messages) {
19
+ const role = determineRole(message.participant, assistantName, userSet);
20
+ const content = transformContent(message.content);
21
+ if (mergeStrategy === 'concatenate' && messages.length > 0) {
22
+ const lastMessage = messages[messages.length - 1];
23
+ if (lastMessage && lastMessage.role === role) {
24
+ // Merge with previous message
25
+ lastMessage.content.push(...content);
26
+ continue;
27
+ }
28
+ }
29
+ messages.push({ role, content });
30
+ }
31
+ // Ensure alternating roles (required by most providers)
32
+ const normalizedMessages = ensureAlternatingRoles(messages);
33
+ // Build stop sequences
34
+ const stopSequences = buildChatStopSequences(request);
35
+ return {
36
+ system: request.system ?? '',
37
+ messages: normalizedMessages,
38
+ stopSequences,
39
+ };
40
+ }
41
+ // ============================================================================
42
+ // Helper Functions
43
+ // ============================================================================
44
+ function determineRole(participant, assistantName, userSet) {
45
+ if (participant === assistantName) {
46
+ return 'assistant';
47
+ }
48
+ if (userSet.has(participant)) {
49
+ return 'user';
50
+ }
51
+ // Default: non-assistant is user
52
+ return 'user';
53
+ }
54
+ function transformContent(blocks) {
55
+ const result = [];
56
+ for (const block of blocks) {
57
+ if (isTextContent(block)) {
58
+ result.push({ type: 'text', text: block.text });
59
+ }
60
+ else if (block.type === 'image' && block.source.type === 'base64') {
61
+ result.push({
62
+ type: 'image',
63
+ source: {
64
+ type: 'base64',
65
+ data: block.source.data,
66
+ mediaType: block.source.mediaType,
67
+ },
68
+ });
69
+ }
70
+ else if (isToolUseContent(block)) {
71
+ result.push({
72
+ type: 'tool_use',
73
+ id: block.id,
74
+ name: block.name,
75
+ input: block.input,
76
+ });
77
+ }
78
+ else if (isToolResultContent(block)) {
79
+ const content = typeof block.content === 'string'
80
+ ? block.content
81
+ : JSON.stringify(block.content);
82
+ result.push({
83
+ type: 'tool_result',
84
+ tool_use_id: block.toolUseId,
85
+ content,
86
+ });
87
+ }
88
+ // Other block types are skipped or could be handled here
89
+ }
90
+ return result;
91
+ }
92
+ function ensureAlternatingRoles(messages) {
93
+ if (messages.length === 0)
94
+ return messages;
95
+ const result = [];
96
+ let lastRole = null;
97
+ for (const message of messages) {
98
+ if (lastRole === message.role) {
99
+ // Insert empty message of opposite role
100
+ const fillerRole = message.role === 'user' ? 'assistant' : 'user';
101
+ result.push({
102
+ role: fillerRole,
103
+ content: [{ type: 'text', text: '...' }],
104
+ });
105
+ }
106
+ result.push(message);
107
+ lastRole = message.role;
108
+ }
109
+ // Ensure starts with user
110
+ const first = result[0];
111
+ if (result.length > 0 && first && first.role === 'assistant') {
112
+ result.unshift({
113
+ role: 'user',
114
+ content: [{ type: 'text', text: '...' }],
115
+ });
116
+ }
117
+ return result;
118
+ }
119
+ function buildChatStopSequences(request) {
120
+ const sequences = [];
121
+ // Add tool-related stop if tools are defined
122
+ if (request.tools && request.tools.length > 0) {
123
+ sequences.push('</function_calls>');
124
+ }
125
+ // Add any explicit stop sequences from request
126
+ if (request.stopSequences) {
127
+ if (Array.isArray(request.stopSequences)) {
128
+ sequences.push(...request.stopSequences);
129
+ }
130
+ else {
131
+ sequences.push(...request.stopSequences.sequences);
132
+ }
133
+ }
134
+ return sequences;
135
+ }
136
+ //# sourceMappingURL=chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/transforms/chat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Transform exports
3
+ */
4
+ export { transformToPrefill, buildContinuationPrefill, type PrefillTransformResult, type PrefillTransformOptions, type ProviderTextBlock, type ProviderImageBlock, type ProviderContentBlock, type ProviderMessage, } from './prefill.js';
5
+ export { transformToChat, type ChatTransformResult, type ChatTransformOptions, type ChatMessage, type ChatContent, } from './chat.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transforms/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Transform exports
3
+ */
4
+ export { transformToPrefill, buildContinuationPrefill, } from './prefill.js';
5
+ export { transformToChat, } from './chat.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transforms/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,kBAAkB,EAClB,wBAAwB,GAOzB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,GAKhB,MAAM,WAAW,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Prefill mode transforms
3
+ *
4
+ * Converts normalized messages to participant-based conversation log format:
5
+ *
6
+ * Alice: Hello there!
7
+ *
8
+ * Bob: Hi Alice!
9
+ *
10
+ * Claude: [assistant continuation starts here...]
11
+ *
12
+ * Key features:
13
+ * - Cache control markers for Anthropic prompt caching
14
+ * - Image flushing (images cause conversation flush to user turn)
15
+ * - Tool injection into conversation
16
+ */
17
+ import type { NormalizedRequest, CacheControl } from '../types/index.js';
18
+ /**
19
+ * Content block in provider format (Anthropic-style)
20
+ * Can include cache_control for prompt caching
21
+ */
22
+ export interface ProviderTextBlock {
23
+ type: 'text';
24
+ text: string;
25
+ cache_control?: CacheControl;
26
+ }
27
+ export interface ProviderImageBlock {
28
+ type: 'image';
29
+ source: {
30
+ type: 'base64';
31
+ media_type: string;
32
+ data: string;
33
+ };
34
+ }
35
+ export type ProviderContentBlock = ProviderTextBlock | ProviderImageBlock;
36
+ export interface ProviderMessage {
37
+ role: 'user' | 'assistant';
38
+ content: string | ProviderContentBlock[];
39
+ }
40
+ export interface PrefillTransformResult {
41
+ /** System prompt content blocks (may have cache_control) */
42
+ systemContent: ProviderContentBlock[];
43
+ /** Messages in provider format (ready for API) */
44
+ messages: ProviderMessage[];
45
+ /** For legacy compatibility: system as string */
46
+ system: string;
47
+ /** For legacy compatibility: user content as string */
48
+ userContent: string;
49
+ /** For legacy compatibility: assistant prefill as string */
50
+ assistantPrefill: string;
51
+ /** Stop sequences to use */
52
+ stopSequences: string[];
53
+ /** Number of cache markers applied */
54
+ cacheMarkersApplied: number;
55
+ }
56
+ export interface PrefillTransformOptions {
57
+ /** Name of the assistant participant (default: 'Claude') */
58
+ assistantName?: string;
59
+ /** Maximum participants to include in stop sequences */
60
+ maxParticipantsForStop?: number;
61
+ /** Custom stop sequences to add */
62
+ additionalStopSequences?: string[];
63
+ /**
64
+ * Where to inject tool definitions:
65
+ * - 'conversation': Inject into assistant content ~N messages from end (default)
66
+ * - 'system': Inject into system prompt
67
+ * - 'none': No injection (use getToolInstructions() for manual placement)
68
+ */
69
+ toolInjectionMode?: 'conversation' | 'system' | 'none';
70
+ /** Position to inject tools when mode is 'conversation' (from end of messages) */
71
+ toolInjectionPosition?: number;
72
+ /** Enable prompt caching (default: true) */
73
+ promptCaching?: boolean;
74
+ /** Message delimiter for base models (e.g., '</s>') */
75
+ messageDelimiter?: string;
76
+ /** Context prefix for simulacrum seeding */
77
+ contextPrefix?: string;
78
+ /** Start assistant response with <thinking> tag */
79
+ prefillThinking?: boolean;
80
+ }
81
+ /**
82
+ * Transform normalized request to prefill format with cache control support
83
+ */
84
+ export declare function transformToPrefill(request: NormalizedRequest, options?: PrefillTransformOptions): PrefillTransformResult;
85
+ /**
86
+ * Build a continuation request from accumulated output
87
+ */
88
+ export declare function buildContinuationPrefill(originalResult: PrefillTransformResult, accumulated: string): PrefillTransformResult;
89
+ //# sourceMappingURL=prefill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefill.d.ts","sourceRoot":"","sources":["../../src/transforms/prefill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAEV,iBAAiB,EAGjB,YAAY,EACb,MAAM,mBAAmB,CAAC;AAQ3B;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,YAAY,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,MAAM,oBAAoB,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAM1E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAAC;CAC1C;AAMD,MAAM,WAAW,sBAAsB;IACrC,4DAA4D;IAC5D,aAAa,EAAE,oBAAoB,EAAE,CAAC;IAEtC,kDAAkD;IAClD,QAAQ,EAAE,eAAe,EAAE,CAAC;IAE5B,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IAEf,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IAEpB,4DAA4D;IAC5D,gBAAgB,EAAE,MAAM,CAAC;IAEzB,4BAA4B;IAC5B,aAAa,EAAE,MAAM,EAAE,CAAC;IAExB,sCAAsC;IACtC,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAMD,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,wDAAwD;IACxD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC,mCAAmC;IACnC,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,cAAc,GAAG,QAAQ,GAAG,MAAM,CAAC;IAEvD,kFAAkF;IAClF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B,4CAA4C;IAC5C,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,4CAA4C;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,mDAAmD;IACnD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAMD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAuOxB;AA4LD;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,sBAAsB,EACtC,WAAW,EAAE,MAAM,GAClB,sBAAsB,CAuBxB"}
@@ -0,0 +1,401 @@
1
+ /**
2
+ * Prefill mode transforms
3
+ *
4
+ * Converts normalized messages to participant-based conversation log format:
5
+ *
6
+ * Alice: Hello there!
7
+ *
8
+ * Bob: Hi Alice!
9
+ *
10
+ * Claude: [assistant continuation starts here...]
11
+ *
12
+ * Key features:
13
+ * - Cache control markers for Anthropic prompt caching
14
+ * - Image flushing (images cause conversation flush to user turn)
15
+ * - Tool injection into conversation
16
+ */
17
+ import { formatToolDefinitions } from '../utils/tool-parser.js';
18
+ // ============================================================================
19
+ // Main Transform Function
20
+ // ============================================================================
21
+ /**
22
+ * Transform normalized request to prefill format with cache control support
23
+ */
24
+ export function transformToPrefill(request, options = {}) {
25
+ const { assistantName = 'Claude', maxParticipantsForStop = 10, additionalStopSequences = [], toolInjectionMode = 'conversation', toolInjectionPosition = 10, promptCaching = true, messageDelimiter = '', contextPrefix, prefillThinking = false, } = options;
26
+ const messages = request.messages;
27
+ const providerMessages = [];
28
+ // Track cache marker GLOBALLY across all flushes
29
+ // Everything BEFORE we see the marker gets cache_control
30
+ // Everything AFTER does NOT
31
+ let passedCacheMarker = false;
32
+ let cacheMarkersApplied = 0;
33
+ // Joiner between messages (if delimiter, no newlines needed)
34
+ const joiner = messageDelimiter ? '' : '\n';
35
+ // Track conversation lines for current section
36
+ let currentConversation = [];
37
+ let lastNonEmptyParticipant = null;
38
+ // Build system prompt
39
+ let systemText = request.system ?? '';
40
+ // Inject tool definitions into system prompt if mode is 'system'
41
+ if (toolInjectionMode === 'system' && request.tools && request.tools.length > 0) {
42
+ const toolsXml = formatToolsForPrefill(request.tools);
43
+ systemText = injectToolsIntoSystem(systemText, toolsXml);
44
+ }
45
+ // System prompt content (with cache_control if enabled)
46
+ const systemContent = [];
47
+ if (systemText) {
48
+ const systemBlock = { type: 'text', text: systemText };
49
+ if (promptCaching) {
50
+ systemBlock.cache_control = { type: 'ephemeral' };
51
+ cacheMarkersApplied++;
52
+ }
53
+ systemContent.push(systemBlock);
54
+ // Note: system content goes in systemContent, not providerMessages
55
+ // Anthropic's API requires system as a top-level parameter
56
+ }
57
+ // Add context prefix as first cached assistant message (for simulacrum seeding)
58
+ if (contextPrefix) {
59
+ const prefixBlock = { type: 'text', text: contextPrefix };
60
+ if (promptCaching) {
61
+ prefixBlock.cache_control = { type: 'ephemeral' };
62
+ cacheMarkersApplied++;
63
+ }
64
+ providerMessages.push({
65
+ role: 'assistant',
66
+ content: [prefixBlock],
67
+ });
68
+ }
69
+ // Process messages
70
+ for (let i = 0; i < messages.length; i++) {
71
+ const message = messages[i];
72
+ if (!message)
73
+ continue;
74
+ const isLastMessage = i === messages.length - 1;
75
+ const isAssistant = message.participant === assistantName;
76
+ const hasCacheMarker = !!message.metadata?.cacheControl;
77
+ // Extract text and images
78
+ const { text, images } = formatContentForPrefill(message.content, message.participant);
79
+ const hasImages = images.length > 0;
80
+ const isEmpty = !text.trim() && !hasImages;
81
+ // Check for tool results
82
+ const hasToolResult = message.content.some(c => c.type === 'tool_result');
83
+ // If message has images, flush current conversation and add as user message
84
+ if (hasImages && !isEmpty) {
85
+ // Flush current assistant conversation
86
+ if (currentConversation.length > 0) {
87
+ const content = currentConversation.join(joiner);
88
+ providerMessages.push({
89
+ role: 'assistant',
90
+ content: content,
91
+ });
92
+ currentConversation = [];
93
+ }
94
+ // Add message with image as user turn
95
+ const userContent = [];
96
+ if (text) {
97
+ userContent.push({ type: 'text', text: `${message.participant}: ${text}` });
98
+ }
99
+ userContent.push(...images);
100
+ providerMessages.push({
101
+ role: 'user',
102
+ content: userContent,
103
+ });
104
+ lastNonEmptyParticipant = message.participant;
105
+ continue;
106
+ }
107
+ // Skip empty messages (except last)
108
+ if (isEmpty && !isLastMessage) {
109
+ continue;
110
+ }
111
+ // Check if this message has the cache marker - switch to uncached mode AFTER this
112
+ if (hasCacheMarker && !passedCacheMarker) {
113
+ // Flush everything before this message WITH cache_control (if caching enabled)
114
+ if (currentConversation.length > 0) {
115
+ const content = currentConversation.join(joiner);
116
+ const contentBlock = { type: 'text', text: content };
117
+ if (promptCaching) {
118
+ contentBlock.cache_control = { type: 'ephemeral' };
119
+ cacheMarkersApplied++;
120
+ }
121
+ providerMessages.push({
122
+ role: 'assistant',
123
+ content: [contentBlock],
124
+ });
125
+ currentConversation = [];
126
+ }
127
+ passedCacheMarker = true;
128
+ }
129
+ // Check bot continuation logic
130
+ const isBotMessage = message.participant === assistantName;
131
+ const isContinuation = isBotMessage && lastNonEmptyParticipant === assistantName && !hasToolResult;
132
+ if (isContinuation && isLastMessage) {
133
+ // Bot continuation - don't add prefix, just complete from where we are
134
+ continue;
135
+ }
136
+ else if (isLastMessage && isEmpty) {
137
+ // Completion target - optionally start with thinking tag
138
+ if (prefillThinking) {
139
+ currentConversation.push(`${message.participant}: <thinking>`);
140
+ }
141
+ else {
142
+ currentConversation.push(`${message.participant}:`);
143
+ }
144
+ }
145
+ else if (text) {
146
+ // Regular message - append delimiter if configured
147
+ currentConversation.push(`${message.participant}: ${text}${messageDelimiter}`);
148
+ if (!hasToolResult) {
149
+ lastNonEmptyParticipant = message.participant;
150
+ }
151
+ }
152
+ }
153
+ // If conversation doesn't end with assistant turn, append one
154
+ // This ensures the model knows to respond as the assistant
155
+ if (lastNonEmptyParticipant !== assistantName) {
156
+ if (prefillThinking) {
157
+ currentConversation.push(`${assistantName}: <thinking>`);
158
+ }
159
+ else {
160
+ currentConversation.push(`${assistantName}:`);
161
+ }
162
+ }
163
+ // Flush any remaining conversation, inject tools if mode is 'conversation'
164
+ if (currentConversation.length > 0) {
165
+ const hasToolsForConversation = toolInjectionMode === 'conversation' &&
166
+ request.tools &&
167
+ request.tools.length > 0;
168
+ if (hasToolsForConversation) {
169
+ // Inject tools into assistant content
170
+ const toolsText = formatToolsForInjection(request.tools);
171
+ if (currentConversation.length > toolInjectionPosition) {
172
+ // Long conversation: insert tools ~N messages from the end
173
+ const splitPoint = currentConversation.length - toolInjectionPosition;
174
+ const beforeTools = currentConversation.slice(0, splitPoint);
175
+ const afterTools = currentConversation.slice(splitPoint);
176
+ const combined = [
177
+ ...beforeTools,
178
+ toolsText,
179
+ ...afterTools,
180
+ ].join(joiner);
181
+ providerMessages.push({
182
+ role: 'assistant',
183
+ content: combined,
184
+ });
185
+ }
186
+ else {
187
+ // Short conversation: inject tools at the end (right before completion point)
188
+ const combined = [...currentConversation, toolsText].join(joiner);
189
+ providerMessages.push({
190
+ role: 'assistant',
191
+ content: combined,
192
+ });
193
+ }
194
+ }
195
+ else {
196
+ // No tool injection needed
197
+ providerMessages.push({
198
+ role: 'assistant',
199
+ content: currentConversation.join(joiner),
200
+ });
201
+ }
202
+ }
203
+ // Build stop sequences from participants
204
+ const stopSequences = buildStopSequences(messages, assistantName, maxParticipantsForStop, additionalStopSequences);
205
+ // Build legacy string versions for backwards compatibility
206
+ const legacyStrings = buildLegacyStrings(providerMessages, systemText);
207
+ return {
208
+ systemContent,
209
+ messages: providerMessages,
210
+ system: systemText,
211
+ userContent: legacyStrings.userContent,
212
+ assistantPrefill: legacyStrings.assistantPrefill,
213
+ stopSequences,
214
+ cacheMarkersApplied,
215
+ };
216
+ }
217
+ // ============================================================================
218
+ // Helper Functions
219
+ // ============================================================================
220
+ function formatContentForPrefill(content, participant) {
221
+ const parts = [];
222
+ const images = [];
223
+ for (const block of content) {
224
+ if (block.type === 'text') {
225
+ parts.push(block.text);
226
+ }
227
+ else if (block.type === 'image') {
228
+ // Convert to provider format
229
+ if (block.source.type === 'base64') {
230
+ images.push({
231
+ type: 'image',
232
+ source: {
233
+ type: 'base64',
234
+ media_type: block.source.mediaType,
235
+ data: block.source.data,
236
+ },
237
+ });
238
+ }
239
+ }
240
+ else if (block.type === 'tool_use') {
241
+ // Format as: Name>[toolname]: {json}
242
+ parts.push(`${participant}>[${block.name}]: ${JSON.stringify(block.input)}`);
243
+ }
244
+ else if (block.type === 'tool_result') {
245
+ // Format as: Name<[tool_result]: result
246
+ const resultText = typeof block.content === 'string'
247
+ ? block.content
248
+ : JSON.stringify(block.content);
249
+ parts.push(`${participant}<[tool_result]: ${resultText}`);
250
+ }
251
+ }
252
+ return { text: parts.join('\n'), images };
253
+ }
254
+ function formatToolsForPrefill(tools) {
255
+ const toolsForPrompt = tools.map((tool) => ({
256
+ name: tool.name,
257
+ description: tool.description,
258
+ parameters: Object.fromEntries(Object.entries(tool.inputSchema.properties).map(([name, schema]) => [
259
+ name,
260
+ {
261
+ type: schema.type,
262
+ description: schema.description,
263
+ required: tool.inputSchema.required?.includes(name),
264
+ enum: schema.enum,
265
+ },
266
+ ])),
267
+ }));
268
+ return formatToolDefinitions(toolsForPrompt);
269
+ }
270
+ function injectToolsIntoSystem(system, toolsXml) {
271
+ const toolsSection = `
272
+ <available_tools>
273
+ ${toolsXml}
274
+ </available_tools>
275
+
276
+ When you want to use a tool, output:
277
+ <function_calls>
278
+ <invoke name="tool_name">
279
+ <parameter name="param_name">value</parameter>
280
+ </invoke>
281
+ </function_calls>
282
+ `;
283
+ return system + '\n\n' + toolsSection;
284
+ }
285
+ // Tool format constants (assembled to avoid triggering stop sequences)
286
+ const FUNCTIONS_OPEN = '<' + 'functions>';
287
+ const FUNCTIONS_CLOSE = '</' + 'functions>';
288
+ const FUNCTION_OPEN = '<' + 'function>';
289
+ const FUNCTION_CLOSE = '</' + 'function>';
290
+ const FUNC_CALLS_OPEN = '<' + 'function_calls>';
291
+ const FUNC_CALLS_CLOSE = '</' + 'function_calls>';
292
+ const INVOKE_OPEN = '<' + 'invoke name="';
293
+ const INVOKE_CLOSE = '</' + 'invoke>';
294
+ const PARAM_OPEN = '<' + 'parameter name="';
295
+ const PARAM_CLOSE = '</' + 'parameter>';
296
+ function formatToolsForInjection(tools) {
297
+ // Use the same XML format as system mode for consistency
298
+ const toolsXml = formatToolsForPrefill(tools);
299
+ return `
300
+ <available_tools>
301
+ ${toolsXml}
302
+ </available_tools>
303
+
304
+ When you want to use a tool, output:
305
+ ${FUNC_CALLS_OPEN}
306
+ ${INVOKE_OPEN}tool_name">
307
+ ${PARAM_OPEN}param_name">value${PARAM_CLOSE}
308
+ ${INVOKE_CLOSE}
309
+ ${FUNC_CALLS_CLOSE}`;
310
+ }
311
+ function buildStopSequences(messages, assistantName, maxParticipants, additionalSequences) {
312
+ // Collect unique participants (excluding assistant)
313
+ const participants = new Set();
314
+ // Scan from end of messages
315
+ for (let i = messages.length - 1; i >= 0 && participants.size < maxParticipants; i--) {
316
+ const message = messages[i];
317
+ if (!message)
318
+ continue;
319
+ const participant = message.participant;
320
+ if (participant !== assistantName) {
321
+ participants.add(participant);
322
+ }
323
+ }
324
+ // Build stop sequences
325
+ const sequences = [];
326
+ // Participant-based stops
327
+ for (const participant of participants) {
328
+ sequences.push(`\n${participant}:`);
329
+ }
330
+ // Tool-related stop
331
+ sequences.push('</function_calls>');
332
+ // Additional sequences
333
+ sequences.push(...additionalSequences);
334
+ return sequences;
335
+ }
336
+ function buildLegacyStrings(messages, systemText) {
337
+ // Extract user content (first user message after system)
338
+ let userContent = '';
339
+ let assistantPrefill = '';
340
+ for (const msg of messages) {
341
+ if (msg.role === 'user') {
342
+ if (typeof msg.content === 'string') {
343
+ userContent += msg.content + '\n\n';
344
+ }
345
+ else {
346
+ // Extract text from blocks
347
+ for (const block of msg.content) {
348
+ if (block.type === 'text') {
349
+ userContent += block.text + '\n\n';
350
+ }
351
+ }
352
+ }
353
+ }
354
+ else if (msg.role === 'assistant') {
355
+ if (typeof msg.content === 'string') {
356
+ assistantPrefill += msg.content;
357
+ }
358
+ else {
359
+ for (const block of msg.content) {
360
+ if (block.type === 'text') {
361
+ assistantPrefill += block.text;
362
+ }
363
+ }
364
+ }
365
+ }
366
+ }
367
+ return {
368
+ userContent: userContent.trim(),
369
+ assistantPrefill: assistantPrefill,
370
+ };
371
+ }
372
+ // ============================================================================
373
+ // Prefill Continuation
374
+ // ============================================================================
375
+ /**
376
+ * Build a continuation request from accumulated output
377
+ */
378
+ export function buildContinuationPrefill(originalResult, accumulated) {
379
+ // Update the last assistant message with accumulated content
380
+ const newMessages = [...originalResult.messages];
381
+ // Find the last assistant message or add one
382
+ const lastIdx = newMessages.length - 1;
383
+ if (lastIdx >= 0 && newMessages[lastIdx]?.role === 'assistant') {
384
+ newMessages[lastIdx] = {
385
+ role: 'assistant',
386
+ content: accumulated,
387
+ };
388
+ }
389
+ else {
390
+ newMessages.push({
391
+ role: 'assistant',
392
+ content: accumulated,
393
+ });
394
+ }
395
+ return {
396
+ ...originalResult,
397
+ messages: newMessages,
398
+ assistantPrefill: accumulated,
399
+ };
400
+ }
401
+ //# sourceMappingURL=prefill.js.map