@animalabs/membrane 0.5.69 → 0.5.71

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 (37) hide show
  1. package/dist/formatters/index.d.ts +1 -0
  2. package/dist/formatters/index.d.ts.map +1 -1
  3. package/dist/formatters/index.js +1 -0
  4. package/dist/formatters/index.js.map +1 -1
  5. package/dist/formatters/native.d.ts.map +1 -1
  6. package/dist/formatters/native.js +6 -0
  7. package/dist/formatters/native.js.map +1 -1
  8. package/dist/formatters/openai-responses.d.ts +23 -0
  9. package/dist/formatters/openai-responses.d.ts.map +1 -0
  10. package/dist/formatters/openai-responses.js +188 -0
  11. package/dist/formatters/openai-responses.js.map +1 -0
  12. package/dist/membrane.d.ts.map +1 -1
  13. package/dist/membrane.js +29 -2
  14. package/dist/membrane.js.map +1 -1
  15. package/dist/providers/anthropic.d.ts.map +1 -1
  16. package/dist/providers/anthropic.js +32 -3
  17. package/dist/providers/anthropic.js.map +1 -1
  18. package/dist/providers/index.d.ts +1 -0
  19. package/dist/providers/index.d.ts.map +1 -1
  20. package/dist/providers/index.js +1 -0
  21. package/dist/providers/index.js.map +1 -1
  22. package/dist/providers/openai-responses-api.d.ts +129 -0
  23. package/dist/providers/openai-responses-api.d.ts.map +1 -0
  24. package/dist/providers/openai-responses-api.js +479 -0
  25. package/dist/providers/openai-responses-api.js.map +1 -0
  26. package/dist/types/content.d.ts +26 -0
  27. package/dist/types/content.d.ts.map +1 -1
  28. package/dist/types/content.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/formatters/index.ts +4 -0
  31. package/src/formatters/native.ts +5 -0
  32. package/src/formatters/openai-responses.ts +220 -0
  33. package/src/membrane.ts +28 -2
  34. package/src/providers/anthropic.ts +32 -4
  35. package/src/providers/index.ts +11 -0
  36. package/src/providers/openai-responses-api.ts +653 -0
  37. package/src/types/content.ts +26 -0
@@ -21,6 +21,14 @@ export interface TextContent {
21
21
  text: string;
22
22
  /** Cache control for Anthropic prompt caching */
23
23
  cache_control?: CacheControl;
24
+ /**
25
+ * Opaque provider-native item this block was derived from (e.g. an OpenAI
26
+ * Responses output item). Provider-native formatters replay it verbatim;
27
+ * other providers must ignore it. A zero-width carrier (`text: ''` plus
28
+ * `rawItem`) has no normalized equivalent and must be filtered out of
29
+ * requests for providers that reject empty text blocks (Anthropic).
30
+ */
31
+ rawItem?: unknown;
24
32
  }
25
33
  export interface ImageContent {
26
34
  type: 'image';
@@ -30,44 +38,60 @@ export interface ImageContent {
30
38
  * can auto-fetch URLs from text (like Gemini 3.x) when inlineData is
31
39
  * not viable (e.g., missing thought_signature on model-role images). */
32
40
  sourceUrl?: string;
41
+ /** See {@link TextContent.rawItem}. */
42
+ rawItem?: unknown;
33
43
  }
34
44
  export interface DocumentContent {
35
45
  type: 'document';
36
46
  source: Base64Source;
37
47
  filename?: string;
48
+ /** See {@link TextContent.rawItem}. */
49
+ rawItem?: unknown;
38
50
  }
39
51
  export interface AudioContent {
40
52
  type: 'audio';
41
53
  source: Base64Source;
42
54
  duration?: number;
55
+ /** See {@link TextContent.rawItem}. */
56
+ rawItem?: unknown;
43
57
  }
44
58
  export interface VideoContent {
45
59
  type: 'video';
46
60
  source: Base64Source;
47
61
  duration?: number;
62
+ /** See {@link TextContent.rawItem}. */
63
+ rawItem?: unknown;
48
64
  }
49
65
  export interface GeneratedImageContent {
50
66
  type: 'generated_image';
51
67
  data: string;
52
68
  mimeType: string;
53
69
  isPreview?: boolean;
70
+ /** See {@link TextContent.rawItem}. */
71
+ rawItem?: unknown;
54
72
  }
55
73
  export interface ToolUseContent {
56
74
  type: 'tool_use';
57
75
  id: string;
58
76
  name: string;
59
77
  input: Record<string, unknown>;
78
+ /** See {@link TextContent.rawItem}. */
79
+ rawItem?: unknown;
60
80
  }
61
81
  export interface ToolResultContent {
62
82
  type: 'tool_result';
63
83
  toolUseId: string;
64
84
  content: string | ContentBlock[];
65
85
  isError?: boolean;
86
+ /** See {@link TextContent.rawItem}. */
87
+ rawItem?: unknown;
66
88
  }
67
89
  export interface ThinkingContent {
68
90
  type: 'thinking';
69
91
  thinking: string;
70
92
  signature?: string;
93
+ /** See {@link TextContent.rawItem}. */
94
+ rawItem?: unknown;
71
95
  }
72
96
  export interface RedactedThinkingContent {
73
97
  type: 'redacted_thinking';
@@ -77,6 +101,8 @@ export interface RedactedThinkingContent {
77
101
  * (the API decrypts it to reconstruct prior reasoning).
78
102
  */
79
103
  data: string;
104
+ /** See {@link TextContent.rawItem}. */
105
+ rawItem?: unknown;
80
106
  }
81
107
  export type ContentBlock = TextContent | ImageContent | DocumentContent | AudioContent | VideoContent | GeneratedImageContent | ToolUseContent | ToolResultContent | ThinkingContent | RedactedThinkingContent;
82
108
  export declare function isTextContent(block: ContentBlock): block is TextContent;
@@ -1 +1 @@
1
- {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/types/content.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/types/content.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/types/content.ts"],"names":[],"mappings":"AAAA;;GAEG;AA8IH,+EAA+E;AAC/E,cAAc;AACd,+EAA+E;AAE/E,MAAM,UAAU,aAAa,CAAC,KAAmB;IAC/C,OAAO,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAmB;IAChD,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAmB;IACnD,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAmB;IAChD,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAmB;IAChD,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAmB;IACzD,OAAO,KAAK,CAAC,IAAI,KAAK,iBAAiB,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAmB;IAClD,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAmB;IACrD,OAAO,KAAK,CAAC,IAAI,KAAK,aAAa,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAmB;IACnD,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,KAAmB;IAC3D,OAAO,KAAK,CAAC,IAAI,KAAK,mBAAmB,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,KAAmB;IAEnB,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACtE,CAAC"}
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/types/content.ts"],"names":[],"mappings":"AAAA;;GAEG;AAwKH,+EAA+E;AAC/E,cAAc;AACd,+EAA+E;AAE/E,MAAM,UAAU,aAAa,CAAC,KAAmB;IAC/C,OAAO,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAmB;IAChD,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAmB;IACnD,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAmB;IAChD,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAmB;IAChD,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAmB;IACzD,OAAO,KAAK,CAAC,IAAI,KAAK,iBAAiB,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAmB;IAClD,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAmB;IACrD,OAAO,KAAK,CAAC,IAAI,KAAK,aAAa,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAmB;IACnD,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,KAAmB;IAC3D,OAAO,KAAK,CAAC,IAAI,KAAK,mBAAmB,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,KAAmB;IAEnB,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACtE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animalabs/membrane",
3
- "version": "0.5.69",
3
+ "version": "0.5.71",
4
4
  "description": "LLM middleware - a selective boundary that transforms what passes through",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,6 +16,10 @@ export type {
16
16
 
17
17
  export { AnthropicXmlFormatter, type AnthropicXmlFormatterConfig } from './anthropic-xml.js';
18
18
  export { NativeFormatter, type NativeFormatterConfig } from './native.js';
19
+ export {
20
+ OpenAIResponsesFormatter,
21
+ OPENAI_RESPONSES_ITEMS_METADATA_KEY,
22
+ } from './openai-responses.js';
19
23
  export { CompletionsFormatter, type CompletionsFormatterConfig } from './completions.js';
20
24
 
21
25
  export {
@@ -395,6 +395,11 @@ export class NativeFormatter implements PrefillFormatter {
395
395
 
396
396
  for (const block of content) {
397
397
  if (block.type === 'text') {
398
+ // Empty text blocks are rejected by the Anthropic API. In particular,
399
+ // zero-width rawItem carriers (opaque provider-native items smuggled
400
+ // through normalized history) must not leak here. Filter BEFORE the
401
+ // name prefix below would make them non-empty.
402
+ if (block.text === '') continue;
398
403
  let text = block.text;
399
404
  if (options.includeNames) {
400
405
  const prefix = this.config.nameFormat.replace('{name}', participant);
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Formatter for stateless, provider-native OpenAI Responses histories.
3
+ *
4
+ * Imported/native items are carried either in message metadata under
5
+ * `openaiResponsesItems` or on content blocks as `rawItem`. Those items are
6
+ * emitted verbatim and in order. Normalized messages created after import are
7
+ * converted to Responses input items without rewriting the native prefix.
8
+ */
9
+
10
+ import type {
11
+ ContentBlock,
12
+ NormalizedMessage,
13
+ ToolCall,
14
+ ToolResult,
15
+ } from '../types/index.js';
16
+ import type {
17
+ BuildOptions,
18
+ BuildResult,
19
+ BlockEvent,
20
+ ParseResult,
21
+ PrefillFormatter,
22
+ StreamEmission,
23
+ StreamParser,
24
+ } from './types.js';
25
+
26
+ export const OPENAI_RESPONSES_ITEMS_METADATA_KEY = 'openaiResponsesItems';
27
+
28
+ class ResponsesPassthroughParser implements StreamParser {
29
+ private accumulated = '';
30
+ private blockIndex = 0;
31
+ private blockStarted = false;
32
+
33
+ push(chunk: string): void { this.accumulated += chunk; }
34
+ processChunk(chunk: string): ParseResult {
35
+ this.accumulated += chunk;
36
+ const meta = { type: 'text' as const, visible: true, blockIndex: this.blockIndex };
37
+ const emissions: StreamEmission[] = [];
38
+ const blockEvents: BlockEvent[] = [];
39
+ if (!this.blockStarted) {
40
+ const event: BlockEvent = { event: 'block_start', index: this.blockIndex, block: { type: 'text' } };
41
+ emissions.push({ kind: 'blockEvent', event });
42
+ blockEvents.push(event);
43
+ this.blockStarted = true;
44
+ }
45
+ emissions.push({ kind: 'content', text: chunk, meta });
46
+ return { emissions, content: [{ text: chunk, meta }], blockEvents };
47
+ }
48
+ flush(): ParseResult {
49
+ const emissions: StreamEmission[] = [];
50
+ const blockEvents: BlockEvent[] = [];
51
+ if (this.blockStarted) {
52
+ const event: BlockEvent = {
53
+ event: 'block_complete', index: this.blockIndex,
54
+ block: { type: 'text', content: this.accumulated },
55
+ };
56
+ emissions.push({ kind: 'blockEvent', event });
57
+ blockEvents.push(event);
58
+ this.blockStarted = false;
59
+ }
60
+ return { emissions, content: [], blockEvents };
61
+ }
62
+ getAccumulated(): string { return this.accumulated; }
63
+ reset(): void { this.accumulated = ''; this.blockIndex = 0; this.blockStarted = false; }
64
+ isInsideBlock(): boolean { return false; }
65
+ getCurrentBlockType() { return 'text' as const; }
66
+ getBlockIndex(): number { return this.blockIndex; }
67
+ incrementBlockIndex(): void { this.blockIndex++; }
68
+ getDepths() { return { functionCalls: 0, functionResults: 0, thinking: 0 }; }
69
+ resetForNewIteration(): void {}
70
+ }
71
+
72
+ type NativeItem = { type?: string; id?: string; [key: string]: unknown };
73
+
74
+ export class OpenAIResponsesFormatter implements PrefillFormatter {
75
+ readonly name = 'openai-responses';
76
+ readonly usesPrefill = false;
77
+
78
+ buildMessages(messages: NormalizedMessage[], options: BuildOptions): BuildResult {
79
+ const items: NativeItem[] = [];
80
+ let hasImportedItems = false;
81
+
82
+ for (const message of messages) {
83
+ const nativeItems = message.metadata?.[OPENAI_RESPONSES_ITEMS_METADATA_KEY];
84
+ if (Array.isArray(nativeItems)) {
85
+ items.push(...nativeItems as NativeItem[]);
86
+ hasImportedItems = true;
87
+ continue;
88
+ }
89
+
90
+ const seenRawItems = new Set<string>();
91
+ let pendingParts: ContentBlock[] = [];
92
+ const flushPending = () => {
93
+ if (pendingParts.length === 0) return;
94
+ items.push(...this.convertBlocks(message, pendingParts, options.assistantParticipant));
95
+ pendingParts = [];
96
+ };
97
+
98
+ for (const block of message.content) {
99
+ const rawItem = block.rawItem as NativeItem | undefined;
100
+ if (!rawItem || typeof rawItem !== 'object') {
101
+ pendingParts.push(block);
102
+ continue;
103
+ }
104
+
105
+ flushPending();
106
+ const key = typeof rawItem.id === 'string'
107
+ ? `${rawItem.type ?? ''}:${rawItem.id}`
108
+ : JSON.stringify(rawItem);
109
+ if (!seenRawItems.has(key)) {
110
+ items.push(rawItem);
111
+ seenRawItems.add(key);
112
+ }
113
+ }
114
+ flushPending();
115
+ }
116
+
117
+ // An imported rollout already carries its own developer/system items, and
118
+ // re-injecting the recipe prompt over them would double the instructions.
119
+ // Suppress the system prompt ONLY for imported histories — signalled by
120
+ // the import metadata key or by a developer/system item in the native
121
+ // prefix. Blocks with `rawItem` do NOT count: every response from this
122
+ // provider attaches rawItem (see parseProviderContent), so keying on it
123
+ // would silently drop a fresh session's system prompt from turn 2 onward
124
+ // (turn 1 sends it as the top-level `instructions` request field, never
125
+ // as an input item, and the adapter is stateless — nothing else retains
126
+ // it). `instructions` is a separate request field, so re-sending it does
127
+ // not perturb the input-item prefix or its caching.
128
+ const hasImportedSystemItem = hasImportedItems ||
129
+ items.some(item =>
130
+ (item.type === 'message' || item.type === undefined) &&
131
+ ((item as { role?: unknown }).role === 'developer' ||
132
+ (item as { role?: unknown }).role === 'system'));
133
+
134
+ return {
135
+ // BuildResult's historical type says chat envelopes, but Membrane's
136
+ // provider boundary deliberately accepts unknown provider-native items.
137
+ messages: items as unknown as BuildResult['messages'],
138
+ systemContent: hasImportedSystemItem ? undefined : options.systemPrompt,
139
+ stopSequences: options.additionalStopSequences ?? [],
140
+ nativeTools: options.tools?.map(tool => ({
141
+ type: 'function',
142
+ name: tool.name,
143
+ description: tool.description,
144
+ parameters: tool.inputSchema,
145
+ })),
146
+ ready: true,
147
+ };
148
+ }
149
+
150
+ private convertBlocks(
151
+ message: NormalizedMessage,
152
+ blocks: ContentBlock[],
153
+ assistantParticipant: string,
154
+ ): NativeItem[] {
155
+ const isAssistant = message.participant === assistantParticipant;
156
+ const out: NativeItem[] = [];
157
+ let messageParts: unknown[] = [];
158
+
159
+ const flushMessage = () => {
160
+ if (messageParts.length === 0) return;
161
+ out.push({
162
+ type: 'message',
163
+ role: isAssistant ? 'assistant' : 'user',
164
+ content: messageParts,
165
+ });
166
+ messageParts = [];
167
+ };
168
+
169
+ for (const block of blocks) {
170
+ if (block.type === 'text') {
171
+ messageParts.push({
172
+ type: isAssistant ? 'output_text' : 'input_text',
173
+ text: block.text,
174
+ });
175
+ } else if (block.type === 'image' && !isAssistant) {
176
+ const source = block.source;
177
+ messageParts.push(source.type === 'url'
178
+ ? { type: 'input_image', image_url: source.url }
179
+ : { type: 'input_image', image_url: `data:${source.mediaType};base64,${source.data}` });
180
+ } else if (block.type === 'tool_use') {
181
+ flushMessage();
182
+ out.push({
183
+ type: 'function_call',
184
+ call_id: block.id,
185
+ name: block.name,
186
+ arguments: JSON.stringify(block.input ?? {}),
187
+ });
188
+ } else if (block.type === 'tool_result') {
189
+ flushMessage();
190
+ out.push({
191
+ type: 'function_call_output',
192
+ call_id: block.toolUseId,
193
+ output: typeof block.content === 'string'
194
+ ? block.content
195
+ : JSON.stringify(block.content),
196
+ });
197
+ } else if (block.type === 'redacted_thinking') {
198
+ flushMessage();
199
+ out.push({ type: 'reasoning', encrypted_content: block.data });
200
+ }
201
+ }
202
+ flushMessage();
203
+ return out;
204
+ }
205
+
206
+ formatToolResults(results: ToolResult[]): string {
207
+ return JSON.stringify(results.map(result => ({
208
+ type: 'function_call_output',
209
+ call_id: result.toolUseId,
210
+ output: result.content,
211
+ })));
212
+ }
213
+
214
+ createStreamParser(): StreamParser { return new ResponsesPassthroughParser(); }
215
+ parseToolCalls(_content: string): ToolCall[] { return []; }
216
+ hasToolUse(_content: string): boolean { return false; }
217
+ parseContentBlocks(content: string): ContentBlock[] {
218
+ return content ? [{ type: 'text', text: content }] : [];
219
+ }
220
+ }
package/src/membrane.ts CHANGED
@@ -236,7 +236,7 @@ export class Membrane {
236
236
  // Auto mode: choose based on formatter
237
237
  // NativeFormatter → native tools via API
238
238
  // AnthropicXmlFormatter (default) → XML tools in prefill
239
- if (this.formatter.name === 'native') {
239
+ if (this.formatter.name === 'native' || this.formatter.name === 'openai-responses') {
240
240
  return 'native';
241
241
  }
242
242
 
@@ -986,6 +986,14 @@ export class Membrane {
986
986
  request: NormalizedRequest,
987
987
  messages: typeof request.messages
988
988
  ): any {
989
+ // Provider-native formatters own their complete input-item shape. The
990
+ // legacy implementation below is intentionally Anthropic-specific; using
991
+ // it for Responses would normalize away item IDs, encrypted reasoning,
992
+ // assistant phases, and compaction items.
993
+ if (this.formatter.name === 'openai-responses') {
994
+ return this.transformRequest({ ...request, messages }, this.formatter).providerRequest;
995
+ }
996
+
989
997
  // Convert messages to provider format
990
998
  const providerMessages: any[] = [];
991
999
 
@@ -1012,6 +1020,11 @@ export class Membrane {
1012
1020
  const includeNamePrefix = !isAssistant;
1013
1021
  for (const block of msg.content) {
1014
1022
  if (block.type === 'text') {
1023
+ // Empty text blocks are rejected by the Anthropic API. In
1024
+ // particular, zero-width rawItem carriers (opaque Responses items,
1025
+ // see parseProviderContent) must not leak here. Filter BEFORE the
1026
+ // name prefix below would make them non-empty.
1027
+ if (block.text === '') continue;
1015
1028
  let text = block.text;
1016
1029
  if (includeNamePrefix && msg.participant) {
1017
1030
  text = `${msg.participant}: ${text}`;
@@ -1174,19 +1187,24 @@ export class Membrane {
1174
1187
  const blocks: ContentBlock[] = [];
1175
1188
  for (const item of content) {
1176
1189
  if (item.type === 'text') {
1177
- blocks.push({ type: 'text', text: item.text });
1190
+ blocks.push({
1191
+ type: 'text', text: item.text,
1192
+ ...(item.rawItem ? { rawItem: item.rawItem } : {}),
1193
+ });
1178
1194
  } else if (item.type === 'tool_use') {
1179
1195
  blocks.push({
1180
1196
  type: 'tool_use',
1181
1197
  id: item.id,
1182
1198
  name: unsanitizeToolName(item.name),
1183
1199
  input: item.input,
1200
+ ...(item.rawItem ? { rawItem: item.rawItem } : {}),
1184
1201
  });
1185
1202
  } else if (item.type === 'thinking') {
1186
1203
  blocks.push({
1187
1204
  type: 'thinking',
1188
1205
  thinking: item.thinking ?? '',
1189
1206
  ...(item.signature ? { signature: item.signature } : {}),
1207
+ ...(item.rawItem ? { rawItem: item.rawItem } : {}),
1190
1208
  });
1191
1209
  } else if (item.type === 'redacted_thinking') {
1192
1210
  // Pass through verbatim — carries the encrypted `data` payload
@@ -1197,6 +1215,14 @@ export class Membrane {
1197
1215
  data: item.data,
1198
1216
  mimeType: item.mimeType,
1199
1217
  });
1218
+ } else if (item.rawItem) {
1219
+ // Opaque Responses items such as encrypted compaction or custom
1220
+ // tool records have no normalized ContentBlock equivalent. Retain a
1221
+ // zero-width carrier so Chronicle and the Responses formatter can
1222
+ // replay the raw item without surfacing synthetic prompt text.
1223
+ // Anthropic-bound conversion paths filter these out (empty text
1224
+ // blocks are a 400 there); the Responses formatter replays rawItem.
1225
+ blocks.push({ type: 'text', text: '', rawItem: item.rawItem });
1200
1226
  }
1201
1227
  }
1202
1228
  return blocks;
@@ -186,6 +186,11 @@ export class AnthropicAdapter implements ProviderAdapter {
186
186
  let outputTokens = 0;
187
187
  let cacheCreationTokens: number | undefined;
188
188
  let cacheReadTokens: number | undefined;
189
+ let cacheCreation5mTokens: number | undefined;
190
+ let cacheCreation1hTokens: number | undefined;
191
+ let hasCacheCreationBreakdown = false;
192
+ let inferenceGeo: string | undefined;
193
+ let serviceTier: string | undefined;
189
194
  let stopReason: string = 'end_turn';
190
195
  let stopSequence: string | undefined;
191
196
  let stopDetails: unknown;
@@ -207,10 +212,22 @@ export class AnthropicAdapter implements ProviderAdapter {
207
212
  resetIdleTimer();
208
213
  if (event.type === 'message_start') {
209
214
  model = event.message.model;
210
- const usage = event.message.usage as unknown as Record<string, number>;
211
- inputTokens = usage.input_tokens ?? 0;
212
- cacheCreationTokens = usage.cache_creation_input_tokens;
213
- cacheReadTokens = usage.cache_read_input_tokens;
215
+ const usage = event.message.usage as unknown as Record<string, unknown>;
216
+ inputTokens = typeof usage.input_tokens === 'number' ? usage.input_tokens : 0;
217
+ cacheCreationTokens = typeof usage.cache_creation_input_tokens === 'number'
218
+ ? usage.cache_creation_input_tokens : undefined;
219
+ cacheReadTokens = typeof usage.cache_read_input_tokens === 'number'
220
+ ? usage.cache_read_input_tokens : undefined;
221
+ const cacheCreation = usage.cache_creation as Record<string, unknown> | undefined;
222
+ if (cacheCreation) {
223
+ hasCacheCreationBreakdown = true;
224
+ cacheCreation5mTokens = typeof cacheCreation.ephemeral_5m_input_tokens === 'number'
225
+ ? cacheCreation.ephemeral_5m_input_tokens : 0;
226
+ cacheCreation1hTokens = typeof cacheCreation.ephemeral_1h_input_tokens === 'number'
227
+ ? cacheCreation.ephemeral_1h_input_tokens : 0;
228
+ }
229
+ inferenceGeo = typeof usage.inference_geo === 'string' ? usage.inference_geo : undefined;
230
+ serviceTier = typeof usage.service_tier === 'string' ? usage.service_tier : undefined;
214
231
 
215
232
  } else if (event.type === 'content_block_start') {
216
233
  currentBlockIndex = event.index;
@@ -324,6 +341,14 @@ export class AnthropicAdapter implements ProviderAdapter {
324
341
  output_tokens: outputTokens,
325
342
  cache_creation_input_tokens: cacheCreationTokens,
326
343
  cache_read_input_tokens: cacheReadTokens,
344
+ ...(hasCacheCreationBreakdown ? {
345
+ cache_creation: {
346
+ ephemeral_5m_input_tokens: cacheCreation5mTokens ?? 0,
347
+ ephemeral_1h_input_tokens: cacheCreation1hTokens ?? 0,
348
+ },
349
+ } : {}),
350
+ ...(inferenceGeo ? { inference_geo: inferenceGeo } : {}),
351
+ ...(serviceTier ? { service_tier: serviceTier } : {}),
327
352
  },
328
353
  },
329
354
  };
@@ -661,6 +686,9 @@ export function toAnthropicContent(blocks: ContentBlock[]): Anthropic.ContentBlo
661
686
  for (const block of blocks) {
662
687
  switch (block.type) {
663
688
  case 'text': {
689
+ // Empty text blocks (including zero-width rawItem carriers for opaque
690
+ // provider-native items) are rejected by the Anthropic API — drop them.
691
+ if (block.text === '') break;
664
692
  const textBlock: any = { type: 'text', text: block.text };
665
693
  // Preserve cache_control if present
666
694
  if (block.cache_control) {
@@ -60,3 +60,14 @@ export {
60
60
  OpenAIResponsesAdapter,
61
61
  type OpenAIResponsesAdapterConfig,
62
62
  } from './openai-responses.js';
63
+
64
+ export {
65
+ OpenAIResponsesAPIAdapter,
66
+ type OpenAIResponsesAPIAdapterConfig,
67
+ type OpenAIResponsesAPIContentBlock,
68
+ type OpenAIResponsesAPIProviderResponse,
69
+ type OpenAIResponsesAPIRequest,
70
+ type OpenAIResponsesAPIResponse,
71
+ type OpenAIResponsesInputItem,
72
+ type OpenAIResponsesOutputItem,
73
+ } from './openai-responses-api.js';