@xsai/shared-chat 0.4.3 → 0.5.0-beta.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/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { WithUnknown, CommonRequestOptions } from '@xsai/shared';
2
2
 
3
- type FinishReason = 'content_filter' | 'error' | 'length' | 'other' | 'stop' | 'tool-calls' | (string & {});
3
+ type FinishReason = 'content_filter' | 'error' | 'length' | 'other' | 'stop' | 'tool-calls' | 'tool_calls' | (string & {});
4
4
 
5
5
  interface AudioContentPart {
6
6
  input_audio: {
@@ -150,6 +150,33 @@ interface ToolChoiceTool {
150
150
  type: 'function';
151
151
  }
152
152
 
153
+ type PrepareStep = (options: PrepareStepOptions) => PrepareStepResult | Promise<PrepareStepResult>;
154
+ interface PrepareStepOptions {
155
+ messages: Message[];
156
+ model: string;
157
+ stepNumber: number;
158
+ steps: CompletionStep[];
159
+ }
160
+ interface PrepareStepResult {
161
+ messages?: Message[];
162
+ model?: string;
163
+ toolChoice?: ToolChoice;
164
+ }
165
+
166
+ type StopCondition = (context: StopContext) => boolean;
167
+ interface StopContext {
168
+ messages: readonly Message[];
169
+ step: StopStep;
170
+ steps: readonly StopStep[];
171
+ }
172
+ interface StopStep {
173
+ finishReason: FinishReason;
174
+ text?: string;
175
+ toolCalls: CompletionToolCall[];
176
+ toolResults: CompletionToolResult[];
177
+ usage?: Usage;
178
+ }
179
+
153
180
  /** @see {@link https://platform.openai.com/docs/api-reference/chat/create} */
154
181
  interface ChatOptions extends CommonRequestOptions {
155
182
  /**
@@ -188,12 +215,12 @@ declare const chat: <T extends WithUnknown<ChatOptions>>(options: T) => Promise<
188
215
 
189
216
  interface DetermineStepTypeOptions {
190
217
  finishReason: FinishReason;
191
- maxSteps: number;
192
218
  stepsLength: number;
193
219
  toolCallsLength: number;
220
+ willContinue: boolean;
194
221
  }
195
222
  /** @internal */
196
- declare const determineStepType: ({ finishReason, maxSteps, stepsLength, toolCallsLength }: DetermineStepTypeOptions) => CompletionStepType;
223
+ declare const determineStepType: ({ finishReason, stepsLength, toolCallsLength, willContinue }: DetermineStepTypeOptions) => CompletionStepType;
197
224
 
198
225
  interface ExecuteToolOptions {
199
226
  abortSignal?: AbortSignal;
@@ -208,5 +235,25 @@ interface ExecuteToolResult {
208
235
  }
209
236
  declare const executeTool: ({ abortSignal, messages, toolCall, tools }: ExecuteToolOptions) => Promise<ExecuteToolResult>;
210
237
 
211
- export { chat, determineStepType, executeTool };
212
- export type { AssistantMessage, AudioContentPart, ChatOptions, CommonContentPart, CompletionStep, CompletionStepType, CompletionToolCall, CompletionToolResult, DetermineStepTypeOptions, DeveloperMessage, ExecuteToolOptions, ExecuteToolResult, FileContentPart, FinishReason, ImageContentPart, Message, RefusalContentPart, SystemMessage, TextContentPart, Tool, ToolCall, ToolChoice, ToolExecuteOptions, ToolExecuteResult, ToolMessage, Usage, UserMessage };
238
+ interface ResolvedStepOptions {
239
+ messages: Message[];
240
+ model: string;
241
+ toolChoice: ToolChoice | undefined;
242
+ }
243
+ interface ResolveStepOptionsOptions extends Pick<WithUnknown<ChatOptions>, 'messages' | 'model' | 'toolChoice'> {
244
+ prepareStep?: PrepareStep;
245
+ stepNumber: number;
246
+ steps: CompletionStep[];
247
+ }
248
+ declare const resolveStepOptions: ({ messages, model, prepareStep, stepNumber, steps, toolChoice }: ResolveStepOptionsOptions) => Promise<ResolvedStepOptions>;
249
+
250
+ declare const and: (...conditions: StopCondition[]) => StopCondition;
251
+ declare const or: (...conditions: StopCondition[]) => StopCondition;
252
+ declare const not: (condition: StopCondition) => StopCondition;
253
+ declare const stepCountAtLeast: (count: number) => StopCondition;
254
+ declare const hasToolCall: (name?: string) => StopCondition;
255
+ /** @internal */
256
+ declare const shouldStop: (stopWhen: StopCondition, context: StopContext) => boolean;
257
+
258
+ export { and, chat, determineStepType, executeTool, hasToolCall, not, or, resolveStepOptions, shouldStop, stepCountAtLeast };
259
+ export type { AssistantMessage, AudioContentPart, ChatOptions, CommonContentPart, CompletionStep, CompletionStepType, CompletionToolCall, CompletionToolResult, DetermineStepTypeOptions, DeveloperMessage, ExecuteToolOptions, ExecuteToolResult, FileContentPart, FinishReason, ImageContentPart, Message, PrepareStep, PrepareStepOptions, PrepareStepResult, RefusalContentPart, ResolveStepOptionsOptions, ResolvedStepOptions, StopCondition, StopContext, StopStep, SystemMessage, TextContentPart, Tool, ToolCall, ToolChoice, ToolExecuteOptions, ToolExecuteResult, ToolMessage, Usage, UserMessage };
package/dist/index.js CHANGED
@@ -1,15 +1,9 @@
1
- import { requestURL, requestHeaders, requestBody, clean, responseCatch } from '@xsai/shared';
1
+ import { requestURL, requestHeaders, requestBody, responseCatch, InvalidToolCallError, InvalidToolInputError, ToolExecutionError } from '@xsai/shared';
2
2
 
3
3
  const chat = async (options) => (options.fetch ?? globalThis.fetch)(requestURL("chat/completions", options.baseURL), {
4
4
  body: requestBody({
5
5
  ...options,
6
- tools: options.tools?.map((tool) => ({
7
- function: clean({
8
- ...tool.function,
9
- returns: void 0
10
- }),
11
- type: "function"
12
- }))
6
+ tools: options.tools?.map(({ execute: _execute, ...tool }) => tool)
13
7
  }),
14
8
  headers: requestHeaders({
15
9
  "Content-Type": "application/json",
@@ -19,11 +13,11 @@ const chat = async (options) => (options.fetch ?? globalThis.fetch)(requestURL("
19
13
  signal: options.abortSignal
20
14
  }).then(responseCatch);
21
15
 
22
- const determineStepType = ({ finishReason, maxSteps, stepsLength, toolCallsLength }) => {
16
+ const determineStepType = ({ finishReason, stepsLength, toolCallsLength, willContinue }) => {
23
17
  if (stepsLength === 0) {
24
18
  return "initial";
25
- } else if (stepsLength < maxSteps) {
26
- if (toolCallsLength > 0 && finishReason === "tool_calls")
19
+ } else if (willContinue) {
20
+ if (toolCallsLength > 0 && ["tool-calls", "tool_calls"].includes(finishReason))
27
21
  return "tool-result";
28
22
  else if (!["error", "length"].includes(finishReason))
29
23
  return "continue";
@@ -42,34 +36,75 @@ const wrapToolResult = (result) => {
42
36
  return JSON.stringify(result);
43
37
  };
44
38
 
39
+ const isAbortError = (error, abortSignal) => abortSignal?.aborted === true && error === abortSignal.reason || error instanceof Error && error.name === "AbortError";
40
+ const parseToolInput = (toolName, input) => {
41
+ try {
42
+ return JSON.parse(input.trim() || "{}");
43
+ } catch (cause) {
44
+ throw new InvalidToolInputError(`Failed to parse tool input for "${toolName}".`, {
45
+ cause,
46
+ toolInput: input,
47
+ toolName
48
+ });
49
+ }
50
+ };
51
+ const runTool = async (tool, options) => {
52
+ try {
53
+ return wrapToolResult(await tool.execute(options.parsedArgs, {
54
+ abortSignal: options.abortSignal,
55
+ messages: options.messages,
56
+ toolCallId: options.toolCall.id
57
+ }));
58
+ } catch (cause) {
59
+ if (isAbortError(cause, options.abortSignal))
60
+ throw cause;
61
+ throw new ToolExecutionError(`Tool "${tool.function.name}" execution failed.`, {
62
+ cause,
63
+ toolCallId: options.toolCall.id,
64
+ toolInput: options.parsedArgs,
65
+ toolName: tool.function.name
66
+ });
67
+ }
68
+ };
45
69
  const executeTool = async ({ abortSignal, messages, toolCall, tools }) => {
46
- const tool = tools?.find((tool2) => tool2.function.name === toolCall.function.name);
70
+ const toolName = toolCall.function.name;
71
+ const toolArguments = toolCall.function.arguments;
72
+ if (toolName == null) {
73
+ throw new InvalidToolCallError(`Missing toolCall.function.name: ${JSON.stringify(toolCall)}`, {
74
+ reason: "missing_name",
75
+ toolCall
76
+ });
77
+ }
78
+ if (toolArguments == null) {
79
+ throw new InvalidToolCallError(`Missing toolCall.function.arguments: ${JSON.stringify(toolCall)}`, {
80
+ reason: "missing_arguments",
81
+ toolCall
82
+ });
83
+ }
84
+ const tool = tools?.find((tool2) => tool2.function.name === toolName);
47
85
  if (!tool) {
48
86
  const availableTools = tools?.map((tool2) => tool2.function.name);
49
87
  const availableToolsErrorMsg = availableTools == null || availableTools.length === 0 ? "No tools are available" : `Available tools: ${availableTools.join(", ")}`;
50
- throw new Error(`Model tried to call unavailable tool "${toolCall.function.name}", ${availableToolsErrorMsg}.`);
88
+ throw new InvalidToolCallError(`Model tried to call unavailable tool "${toolName}", ${availableToolsErrorMsg}.`, {
89
+ availableTools,
90
+ reason: "unknown_tool",
91
+ toolCall,
92
+ toolName
93
+ });
51
94
  }
52
- if (toolCall.function.name == null)
53
- throw new Error(`Missing toolCall.function.name: ${JSON.stringify(toolCall)}`);
54
- if (toolCall.function.arguments == null)
55
- throw new Error(`Missing toolCall.function.arguments: ${JSON.stringify(toolCall)}`);
56
- const parsedArgs = JSON.parse(toolCall.function.arguments);
57
- const result = wrapToolResult(await tool.execute(parsedArgs, {
58
- abortSignal,
59
- messages,
60
- toolCallId: toolCall.id
61
- }));
95
+ const parsedArgs = parseToolInput(toolName, toolArguments);
96
+ const result = await runTool(tool, { abortSignal, messages, parsedArgs, toolCall });
62
97
  const completionToolCall = {
63
- args: toolCall.function.arguments,
98
+ args: toolArguments,
64
99
  toolCallId: toolCall.id,
65
100
  toolCallType: toolCall.type,
66
- toolName: toolCall.function.name
101
+ toolName
67
102
  };
68
103
  const completionToolResult = {
69
104
  args: parsedArgs,
70
105
  result,
71
106
  toolCallId: toolCall.id,
72
- toolName: toolCall.function.name
107
+ toolName
73
108
  };
74
109
  const message = {
75
110
  content: result,
@@ -83,4 +118,25 @@ const executeTool = async ({ abortSignal, messages, toolCall, tools }) => {
83
118
  };
84
119
  };
85
120
 
86
- export { chat, determineStepType, executeTool };
121
+ const resolveStepOptions = async ({ messages, model, prepareStep, stepNumber, steps, toolChoice }) => {
122
+ const prepared = prepareStep == null ? void 0 : await prepareStep({
123
+ messages: structuredClone(messages),
124
+ model,
125
+ stepNumber,
126
+ steps: structuredClone(steps)
127
+ });
128
+ return {
129
+ messages: prepared?.messages != null ? structuredClone(prepared.messages) : messages,
130
+ model: prepared?.model ?? model,
131
+ toolChoice: prepared?.toolChoice ?? toolChoice
132
+ };
133
+ };
134
+
135
+ const and = (...conditions) => (context) => conditions.every((condition) => condition(context));
136
+ const or = (...conditions) => (context) => conditions.some((condition) => condition(context));
137
+ const not = (condition) => (context) => !condition(context);
138
+ const stepCountAtLeast = (count) => ({ steps }) => steps.length >= count;
139
+ const hasToolCall = (name) => ({ step }) => step.toolCalls.some((toolCall) => name == null || toolCall.toolName === name);
140
+ const shouldStop = (stopWhen, context) => stopWhen(context);
141
+
142
+ export { and, chat, determineStepType, executeTool, hasToolCall, not, or, resolveStepOptions, shouldStop, stepCountAtLeast };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xsai/shared-chat",
3
3
  "type": "module",
4
- "version": "0.4.3",
4
+ "version": "0.5.0-beta.1",
5
5
  "description": "extra-small AI SDK.",
6
6
  "author": "Moeru AI",
7
7
  "license": "MIT",
@@ -29,7 +29,7 @@
29
29
  "dist"
30
30
  ],
31
31
  "dependencies": {
32
- "@xsai/shared": "~0.4.3"
32
+ "@xsai/shared": "~0.5.0-beta.1"
33
33
  },
34
34
  "scripts": {
35
35
  "build": "pkgroll"