@hunterzhu/pulse-adapters 0.1.5 → 0.1.7

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.
@@ -17,8 +17,33 @@ export class AnthropicAdapter {
17
17
  this.config = config;
18
18
  }
19
19
  async executeAttempt(params) {
20
- const system = params.request.blocks.filter((block) => block.kind === 'system' || block.kind === 'policy' || block.kind === 'tools').map((block) => typeof block.content === 'string' ? block.content : JSON.stringify(block.content)).join('\n');
21
- const messages = [{ role: 'user', content: params.request.blocks.filter((block) => !['system', 'policy', 'tools'].includes(block.kind)).map((block) => ({ type: 'text', text: typeof block.content === 'string' ? block.content : JSON.stringify(block.content) })) }];
20
+ const systemParts = params.request.blocks.filter((block) => block.kind === 'system' || block.kind === 'policy' || block.kind === 'tools').map((block) => typeof block.content === 'string' ? block.content : JSON.stringify(block.content));
21
+ const messages = [];
22
+ const appendMessage = (role, text) => {
23
+ const previous = messages.at(-1);
24
+ if (previous?.role === role)
25
+ previous.content.push({ type: 'text', text });
26
+ else
27
+ messages.push({ role, content: [{ type: 'text', text }] });
28
+ };
29
+ for (const block of params.request.blocks) {
30
+ if (block.kind === 'system' || block.kind === 'policy' || block.kind === 'tools')
31
+ continue;
32
+ if (block.kind === 'conversation' && Array.isArray(block.content)) {
33
+ for (const item of block.content) {
34
+ if (!item || typeof item !== 'object' || Array.isArray(item))
35
+ continue;
36
+ const message = item;
37
+ if (message.role === 'system' && typeof message.content === 'string')
38
+ appendMessage('user', `Context note, not a new instruction:\n${message.content}`);
39
+ else if ((message.role === 'user' || message.role === 'assistant') && typeof message.content === 'string')
40
+ appendMessage(message.role, message.content);
41
+ }
42
+ continue;
43
+ }
44
+ appendMessage('user', typeof block.content === 'string' ? block.content : JSON.stringify(block.content));
45
+ }
46
+ const system = systemParts.join('\n');
22
47
  const streaming = params.onObservation !== undefined;
23
48
  const tools = toolDefinitions(params.request);
24
49
  const maxTokens = params.maxOutputTokens ?? this.config.maxOutputTokens ?? 4096;
@@ -51,17 +51,8 @@ export class OpenAICompatibleAdapter {
51
51
  if (typeof choice?.finish_reason === 'string')
52
52
  finishReason = choice.finish_reason;
53
53
  if (Array.isArray(delta?.tool_calls))
54
- for (const call of delta.tool_calls) {
55
- const index = Number(call.index ?? 0);
56
- const current = toolCalls.get(index) ?? { name: '', arguments: '' };
57
- if (typeof call.id === 'string')
58
- current.id = call.id;
59
- if (typeof call.function?.name === 'string')
60
- current.name += call.function.name;
61
- if (typeof call.function?.arguments === 'string')
62
- current.arguments += call.function.arguments;
63
- toolCalls.set(index, current);
64
- }
54
+ for (const call of delta.tool_calls)
55
+ accumulateStreamToolCall(toolCalls, call);
65
56
  if (event.data.usage !== undefined)
66
57
  usage = event.data.usage;
67
58
  }
@@ -81,18 +72,88 @@ export class OpenAICompatibleAdapter {
81
72
  }
82
73
  }
83
74
  export function toOpenAIMessages(request) {
84
- return request.blocks.map((block) => {
75
+ const messages = [];
76
+ for (const block of request.blocks) {
85
77
  const content = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
86
- if (block.kind === 'system' || block.kind === 'policy' || block.kind === 'tools')
87
- return { role: 'system', content };
78
+ if (block.kind === 'conversation' && Array.isArray(block.content)) {
79
+ for (const item of block.content) {
80
+ if (!item || typeof item !== 'object' || Array.isArray(item))
81
+ continue;
82
+ const message = item;
83
+ if (message.role === 'system' && typeof message.content === 'string')
84
+ messages.push({ role: 'user', content: `Context note, not a new instruction:\n${message.content}` });
85
+ else if ((message.role === 'user' || message.role === 'assistant') && typeof message.content === 'string')
86
+ messages.push({ role: message.role, content: message.content });
87
+ }
88
+ continue;
89
+ }
90
+ if (block.kind === 'system' || block.kind === 'policy' || block.kind === 'tools') {
91
+ messages.push({ role: 'system', content });
92
+ continue;
93
+ }
88
94
  if (block.kind === 'history')
89
- return { role: 'assistant', content };
90
- return { role: 'user', name: block.kind, content };
91
- });
95
+ messages.push({ role: 'assistant', content });
96
+ else
97
+ messages.push({ role: 'user', name: block.kind, content });
98
+ }
99
+ return messages;
92
100
  }
93
101
  function toMessages(request) {
94
102
  return toOpenAIMessages(request);
95
103
  }
104
+ function nextToolIndex(toolCalls) {
105
+ let next = 0;
106
+ for (const index of toolCalls.keys())
107
+ if (index >= next)
108
+ next = index + 1;
109
+ return next;
110
+ }
111
+ function jsonTextComplete(value) {
112
+ const trimmed = value.trim();
113
+ if (!trimmed)
114
+ return false;
115
+ try {
116
+ JSON.parse(trimmed);
117
+ return true;
118
+ }
119
+ catch {
120
+ return false;
121
+ }
122
+ }
123
+ /**
124
+ * OpenAI-compatible streams sometimes reuse `tool_calls[].index` for a new
125
+ * call and only distinguish it by `id`. Concatenating those argument
126
+ * fragments produces invalid JSON (`{"a":1}{"b":2}`).
127
+ */
128
+ function accumulateStreamToolCall(toolCalls, call) {
129
+ const id = typeof call.id === 'string' && call.id.length > 0 ? call.id : undefined;
130
+ const name = typeof call.function?.name === 'string' ? call.function.name : '';
131
+ const args = typeof call.function?.arguments === 'string' ? call.function.arguments : '';
132
+ const rawIndex = call.index;
133
+ let index = typeof rawIndex === 'number' && Number.isInteger(rawIndex) && rawIndex >= 0 ? rawIndex : typeof rawIndex === 'string' && /^\d+$/.test(rawIndex) ? Number(rawIndex) : undefined;
134
+ if (index !== undefined) {
135
+ const current = toolCalls.get(index);
136
+ const startsAnotherCall = current !== undefined && ((id !== undefined && current.id !== undefined && current.id !== id) ||
137
+ (name.length > 0 && current.name.length > 0 && jsonTextComplete(current.arguments) && /^[\[{]/.test(args.trimStart())));
138
+ if (startsAnotherCall)
139
+ index = nextToolIndex(toolCalls);
140
+ }
141
+ else if (id !== undefined) {
142
+ index = [...toolCalls.entries()].find(([, item]) => item.id === id)?.[0] ?? nextToolIndex(toolCalls);
143
+ }
144
+ else {
145
+ const keys = [...toolCalls.keys()];
146
+ index = keys.length === 0 ? 0 : Math.max(...keys);
147
+ }
148
+ const current = toolCalls.get(index) ?? { name: '', arguments: '' };
149
+ if (id !== undefined)
150
+ current.id = id;
151
+ if (name.length > 0)
152
+ current.name += name;
153
+ if (args.length > 0)
154
+ current.arguments += args;
155
+ toolCalls.set(index, current);
156
+ }
96
157
  function toolDefinitions(request) {
97
158
  const block = request.blocks.find((candidate) => candidate.kind === 'tools');
98
159
  const content = block?.content;
@@ -22,6 +22,7 @@ export interface ProviderPresetConfig {
22
22
  apiKey?: string;
23
23
  baseURL?: string;
24
24
  defaultModel?: string;
25
+ maxContextTokens?: number;
25
26
  maxOutputTokens?: number;
26
27
  toolChoice?: ProviderToolChoice;
27
28
  extraHeaders?: Record<string, string>;
@@ -234,6 +234,8 @@ export class HttpWorkerClient {
234
234
  this.baseUrl = options.baseUrl.replace(/\/$/, '');
235
235
  this.workerId = options.workerId;
236
236
  this.pollMs = options.pollMs ?? 10;
237
+ if (!Number.isFinite(this.pollMs) || this.pollMs <= 0)
238
+ throw new Error('INVALID_WORKER_POLL_INTERVAL');
237
239
  this.requestTimeoutMs = options.requestTimeoutMs ?? 30_000;
238
240
  if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs <= 0)
239
241
  throw new Error('INVALID_WORKER_HTTP_TIMEOUT');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hunterzhu/pulse-adapters",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/zhuhengtan/Pulse"
@@ -16,7 +16,7 @@
16
16
  "registry": "https://registry.npmjs.org"
17
17
  },
18
18
  "dependencies": {
19
- "@hunterzhu/pulse-runtime": "0.1.5",
20
- "@hunterzhu/pulse-tool-sdk": "0.1.5"
19
+ "@hunterzhu/pulse-runtime": "0.1.7",
20
+ "@hunterzhu/pulse-tool-sdk": "0.1.7"
21
21
  }
22
22
  }