arcane-os 0.3.0 → 0.3.2
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/CHANGELOG.md +27 -0
- package/README.md +86 -117
- package/bin/arcane-test.mjs +170 -46
- package/browser-runtime/ai/browser-speech-artifacts.mjs +887 -909
- package/browser-runtime/ai/browser-speech-providers.mjs +96 -152
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
- package/browser-runtime/ai/browser-wasm.mjs +24 -35
- package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
- package/browser-runtime/ai/model-controller.mjs +584 -181
- package/browser-runtime/ai/speech-worker-client.mjs +8 -146
- package/browser-runtime/ai/speech-worker-runtime.mjs +643 -363
- package/browser-runtime/dom-event-instrumentation.mjs +55 -147
- package/browser-runtime/event-manager.mjs +239 -624
- package/package.json +5 -6
- package/runtime/arcane/components/app-bar.html +3 -15
- package/runtime/arcane/components/assistant-panel.html +10 -10
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chat.html +1359 -135
- package/runtime/arcane/components/conversation-view.html +2 -2
- package/runtime/arcane/components/document-inspector.html +11 -17
- package/runtime/arcane/components/file-manager.html +13 -56
- package/runtime/arcane/components/markdown-document.html +82 -281
- package/runtime/arcane/components/markdown-editor.html +7 -10
- package/runtime/arcane/components/media-embed.html +6 -6
- package/runtime/arcane/components/screen-capture.html +4 -4
- package/runtime/arcane/components/source-explanation.html +2 -2
- package/runtime/arcane/components/speech.html +112 -68
- package/runtime/arcane/components/terminal-workspace.html +4 -4
- package/runtime/arcane/components/theme-editor.html +1 -1
- package/runtime/arcane/components/unified-inbox.html +2 -2
- package/runtime/arcane/components/voice-transcription.html +31 -21
- package/runtime/arcane/entities/Calculation.js +2 -3
- package/runtime/arcane/entities/Chat.js +228 -43
- package/runtime/arcane/entities/Preference.js +3 -5
- package/runtime/arcane/entities/Weather.js +5 -5
- package/runtime/arcane/modules/AI.js +1050 -427
- package/runtime/arcane/modules/AIProviderRuntime.js +658 -363
- package/runtime/arcane/modules/AIResponseLength.js +9 -19
- package/runtime/arcane/modules/AIRuntimeState.js +109 -72
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
- package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
- package/runtime/arcane/modules/CalculatorEngine.js +9 -9
- package/runtime/arcane/modules/CommunicationAppController.js +3 -7
- package/runtime/arcane/modules/ComponentContracts.js +30 -32
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
- package/runtime/arcane/modules/ConversationActionItems.js +26 -59
- package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
- package/runtime/arcane/modules/ConversationTimebox.js +27 -15
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
- package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
- package/runtime/arcane/modules/HTMLImport.js +54 -1
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
- package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
- package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
- package/runtime/arcane/modules/MD.js +1 -45
- package/runtime/arcane/modules/Mail.js +51 -103
- package/runtime/arcane/modules/MailOutbox.mjs +95 -193
- package/runtime/arcane/modules/MailTransport.mjs +36 -57
- package/runtime/arcane/modules/ModelDefinition.js +22 -106
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
- package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
- package/runtime/arcane/modules/PreferenceStore.js +102 -30
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
- package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
- package/runtime/arcane/modules/ScreenCapture.js +175 -128
- package/runtime/arcane/modules/SpeechPlayback.js +46 -149
- package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
- package/runtime/arcane/modules/ToolCallRouter.js +25 -12
- package/runtime/arcane/modules/YouTubeMedia.js +6 -5
- package/schemas/arcane-app-bundle.schema.json +13 -78
- package/schemas/arcane-app.schema.json +9 -25
- package/schemas/arcane-lock.schema.json +18 -151
- package/schemas/arcane-package.schema.json +2 -16
- package/schemas/native-build-plan.schema.json +119 -122
- package/src/app-descriptor.mjs +75 -132
- package/src/application-tests.mjs +200 -0
- package/src/cli/main.mjs +27 -46
- package/src/constants.mjs +3 -4
- package/src/dev-server.mjs +30 -324
- package/src/doctor.mjs +92 -154
- package/src/dom-event-instrumentation.mjs +55 -147
- package/src/errors.mjs +2 -3
- package/src/event-manager.mjs +239 -624
- package/src/event-queue.mjs +3 -3
- package/src/import-map.mjs +273 -1028
- package/src/index.mjs +14 -16
- package/src/installed-sdk-runtime.mjs +40 -62
- package/src/integrated-provider-loader.mjs +53 -382
- package/src/mail-api.mjs +0 -2
- package/src/mail-server.mjs +224 -580
- package/src/mail.mjs +4 -10
- package/src/native-plan.mjs +163 -598
- package/src/native-provider-loader.mjs +104 -1063
- package/src/packager/core.mjs +485 -3229
- package/src/process.mjs +5 -10
- package/src/release-bundle.mjs +292 -2405
- package/src/runtime.mjs +76 -396
- package/src/scaffold.mjs +30 -80
- package/src/sdk-browser-runtime.mjs +70 -626
- package/src/source-server.mjs +588 -0
- package/src/targets/index.mjs +78 -188
- package/src/templates/workspace-template.mjs +19 -135
- package/src/testing-loader.mjs +164 -0
- package/src/testing.mjs +1 -1
- package/src/toolchain.mjs +131 -544
- package/src/update-check.mjs +26 -64
- package/src/workspace-operation-lock.mjs +139 -430
- package/src/workspace-runtime.mjs +112 -779
- package/src/workspace.mjs +40 -302
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
- package/browser-runtime/ai/internal/sha256.mjs +0 -166
- package/docs/architecture.md +0 -344
- package/docs/compatibility.md +0 -36
- package/docs/event-manager.md +0 -294
- package/docs/platform-targets.md +0 -108
- package/docs/publishing.md +0 -201
- package/docs/reference/README.md +0 -187
- package/docs/reference/ai/browser-speech-package-authority.json +0 -835
- package/docs/reference/ai/browser-speech.md +0 -1252
- package/docs/reference/ai/browser-wasm.md +0 -530
- package/docs/reference/arcane-ollama.md +0 -288
- package/docs/reference/availability-and-normalization.md +0 -183
- package/docs/reference/behavioral-testing.md +0 -133
- package/docs/reference/cli.md +0 -779
- package/docs/reference/core/README.md +0 -62
- package/docs/reference/core/arcane-ai-contracts.md +0 -907
- package/docs/reference/core/arcane-api.md +0 -601
- package/docs/reference/core/arcane-entities.md +0 -65
- package/docs/reference/core/arcane-events.md +0 -134
- package/docs/reference/core/ollama-module.md +0 -181
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
- package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
- package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
- package/docs/reference/event-manager.md +0 -1511
- package/docs/reference/inventory/package-api.json +0 -3284
- package/docs/reference/inventory/runtime-components.json +0 -1011
- package/docs/reference/inventory/runtime-entities.json +0 -26
- package/docs/reference/inventory/runtime-modules.json +0 -1431
- package/docs/reference/mail.md +0 -316
- package/docs/reference/protocols.md +0 -677
- package/docs/reference/runtime-components.md +0 -1366
- package/docs/reference/runtime-entities.md +0 -303
- package/docs/reference/runtime-modules.md +0 -2960
- package/docs/reference/sdk-api.md +0 -6694
- package/docs/roadmap.md +0 -79
- package/docs/work-amplification.md +0 -129
- package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
const FORBIDDEN_REQUEST_FIELDS=new Set([
|
|
2
|
+
'messages',
|
|
3
|
+
'onChunk',
|
|
4
|
+
'onResponse',
|
|
5
|
+
'onToolCall',
|
|
6
|
+
'signal',
|
|
7
|
+
'stream',
|
|
8
|
+
]);
|
|
9
9
|
|
|
10
10
|
function isPlainRecord(value){
|
|
11
11
|
return Boolean(value)
|
|
@@ -15,38 +15,26 @@ function isPlainRecord(value){
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function coded(error,code){
|
|
18
|
-
error.code=code;
|
|
18
|
+
if(!error.code) error.code=code;
|
|
19
19
|
return error;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function
|
|
23
|
-
if(!Number.isSafeInteger(value)||value<minimum||value>maximum){
|
|
24
|
-
throw new RangeError(`${label} must be an integer between ${minimum} and ${maximum}.`);
|
|
25
|
-
}
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function boundedContent(value,label,maximum,{optional=false}={}){
|
|
22
|
+
function contentText(value,label,{optional=false}={}){
|
|
30
23
|
if(typeof value!=='string') throw new TypeError(`${label} must be a string.`);
|
|
31
24
|
if(!value.trim()){
|
|
32
25
|
if(optional) return null;
|
|
33
26
|
throw new TypeError(`${label} must contain text.`);
|
|
34
27
|
}
|
|
35
|
-
if(value.length>maximum) throw new RangeError(`${label} exceeds ${maximum} characters.`);
|
|
36
28
|
return value;
|
|
37
29
|
}
|
|
38
30
|
|
|
39
|
-
function optionalMetadata(value,label
|
|
31
|
+
function optionalMetadata(value,label){
|
|
40
32
|
if(value===undefined||value===null||value==='') return null;
|
|
41
33
|
if(typeof value!=='string'){
|
|
42
34
|
throw coded(new TypeError(`${label} must be a string when provided.`),'AI_CHAT_INVALID_RESPONSE');
|
|
43
35
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if(normalized.length>maximum){
|
|
47
|
-
throw coded(new RangeError(`${label} exceeds ${maximum} characters.`),'AI_CHAT_INVALID_RESPONSE');
|
|
48
|
-
}
|
|
49
|
-
return normalized;
|
|
36
|
+
if(!value.trim()) return null;
|
|
37
|
+
return value;
|
|
50
38
|
}
|
|
51
39
|
|
|
52
40
|
function usageCount(value){
|
|
@@ -84,102 +72,206 @@ function assertMessageKeys(value,allowed,label){
|
|
|
84
72
|
if(unknown) throw new TypeError(`${label} contains an unsupported field: ${unknown}.`);
|
|
85
73
|
}
|
|
86
74
|
|
|
87
|
-
function
|
|
88
|
-
if(value
|
|
89
|
-
|
|
90
|
-
throw new TypeError(`${label} must contain exactly one structural tool call.`);
|
|
75
|
+
function toolCallArgumentMessage(value,label){
|
|
76
|
+
if(typeof value!=='string'){
|
|
77
|
+
throw new TypeError(`${label} must be a JSON string.`);
|
|
91
78
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
79
|
+
let argumentsRecord;
|
|
80
|
+
try{
|
|
81
|
+
argumentsRecord=JSON.parse(value);
|
|
82
|
+
}catch(error){
|
|
83
|
+
throw coded(
|
|
84
|
+
new TypeError(`${label} must encode a JSON object.`,{cause:error}),
|
|
85
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if(!isPlainRecord(argumentsRecord)){
|
|
89
|
+
throw coded(
|
|
90
|
+
new TypeError(`${label} must encode a JSON object.`),
|
|
91
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if(typeof argumentsRecord.message!=='string'||!argumentsRecord.message.trim()){
|
|
95
|
+
throw coded(
|
|
96
|
+
new TypeError(`${label}.message must contain user-facing text.`),
|
|
97
|
+
'AI_CHAT_TOOL_MESSAGE_REQUIRED',
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return argumentsRecord.message;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function requireToolMessageSchemas(value,label){
|
|
104
|
+
if(value===undefined) return;
|
|
105
|
+
if(!Array.isArray(value)) throw new TypeError(`${label} must be an array.`);
|
|
106
|
+
for(let index=0;index<value.length;index++){
|
|
107
|
+
const tool=value[index];
|
|
108
|
+
const parameters=tool?.function?.parameters;
|
|
109
|
+
const messageSchema=parameters?.properties?.message;
|
|
110
|
+
if(
|
|
111
|
+
!isPlainRecord(tool)
|
|
112
|
+
||tool.type!=='function'
|
|
113
|
+
||!isPlainRecord(tool.function)
|
|
114
|
+
||!isPlainRecord(parameters)
|
|
115
|
+
||parameters.type!=='object'
|
|
116
|
+
||!isPlainRecord(parameters.properties)
|
|
117
|
+
||!isPlainRecord(messageSchema)
|
|
118
|
+
||messageSchema.type!=='string'
|
|
119
|
+
||!Number.isInteger(messageSchema.minLength)
|
|
120
|
+
||messageSchema.minLength<1
|
|
121
|
+
||!Array.isArray(parameters.required)
|
|
122
|
+
||!parameters.required.includes('message')
|
|
123
|
+
){
|
|
124
|
+
throw coded(
|
|
125
|
+
new TypeError(
|
|
126
|
+
`${label}[${index}] must require a nonempty string parameters.properties.message.`
|
|
127
|
+
),
|
|
128
|
+
'AI_CHAT_TOOL_MESSAGE_REQUIRED',
|
|
129
|
+
);
|
|
99
130
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function normalizeRequestOptions(value,label){
|
|
135
|
+
if(value===undefined) return {};
|
|
136
|
+
if(!isPlainRecord(value)) throw new TypeError(`${label} must be a plain object.`);
|
|
137
|
+
const forbidden=Object.keys(value).find(key=>FORBIDDEN_REQUEST_FIELDS.has(key));
|
|
138
|
+
if(forbidden) throw new TypeError(`${label}.${forbidden} is managed by the chat session.`);
|
|
139
|
+
requireToolMessageSchemas(value.tools,`${label}.tools`);
|
|
140
|
+
if(value.parallelToolCalls===true||value.parallel_tool_calls===true){
|
|
141
|
+
throw coded(
|
|
142
|
+
new TypeError(`${label} cannot enable parallel structural tool calls.`),
|
|
143
|
+
'AI_CHAT_PARALLEL_TOOLS_UNSUPPORTED',
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return {...value};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function normalizeStructuralToolCall(call,label='Structural tool call'){
|
|
150
|
+
try{
|
|
151
|
+
if(!isPlainRecord(call)) throw new TypeError(`${label} must be a plain object.`);
|
|
152
|
+
assertMessageKeys(call,new Set(['function','id','type']),label);
|
|
153
|
+
if(typeof call.id!=='string'||!call.id.trim()){
|
|
154
|
+
throw new TypeError(`${label}.id must contain text.`);
|
|
107
155
|
}
|
|
108
|
-
if(
|
|
109
|
-
|
|
156
|
+
if(call.type!=='function') throw new TypeError(`${label}.type must be function.`);
|
|
157
|
+
if(!isPlainRecord(call.function)) throw new TypeError(`${label}.function must be a plain object.`);
|
|
158
|
+
assertMessageKeys(call.function,new Set(['arguments','name']),`${label}.function`);
|
|
159
|
+
if(typeof call.function.name!=='string'||!call.function.name.trim()){
|
|
160
|
+
throw new TypeError(`${label}.function.name must contain text.`);
|
|
110
161
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
162
|
+
toolCallArgumentMessage(call.function.arguments,`${label}.function.arguments`);
|
|
163
|
+
}catch(error){
|
|
164
|
+
throw coded(error,'AI_CHAT_INVALID_TOOL_CALL');
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
function:{
|
|
168
|
+
arguments:call.function.arguments,
|
|
169
|
+
name:call.function.name,
|
|
170
|
+
},
|
|
171
|
+
id:call.id,
|
|
172
|
+
type:'function',
|
|
173
|
+
};
|
|
120
174
|
}
|
|
121
175
|
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
176
|
+
function normalizeToolCalls(value,label){
|
|
177
|
+
if(value===undefined) return null;
|
|
178
|
+
if(!Array.isArray(value)){
|
|
179
|
+
throw new TypeError(`${label} must be an array of structural tool calls.`);
|
|
180
|
+
}
|
|
181
|
+
if(value.length>1){
|
|
182
|
+
throw coded(
|
|
183
|
+
new TypeError(`${label} must contain at most one structural tool call.`),
|
|
184
|
+
'AI_CHAT_PARALLEL_TOOLS_UNSUPPORTED',
|
|
185
|
+
);
|
|
126
186
|
}
|
|
127
|
-
|
|
187
|
+
const ids=new Set();
|
|
188
|
+
return value.map((call,index)=>{
|
|
189
|
+
const normalized=normalizeStructuralToolCall(call,`${label}[${index}]`);
|
|
190
|
+
if(ids.has(normalized.id)){
|
|
191
|
+
throw coded(
|
|
192
|
+
new TypeError(`${label} contains a duplicate id.`),
|
|
193
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
ids.add(normalized.id);
|
|
197
|
+
return normalized;
|
|
198
|
+
});
|
|
128
199
|
}
|
|
129
200
|
|
|
130
|
-
function normalizeMessage(value,label,
|
|
201
|
+
function normalizeMessage(value,label,allowedRoles){
|
|
131
202
|
if(!isPlainRecord(value)||!allowedRoles.has(value.role)){
|
|
132
203
|
throw new TypeError(`${label} has an unsupported role.`);
|
|
133
204
|
}
|
|
134
205
|
const allowed=new Set(['content','role']);
|
|
135
|
-
if(value.role==='assistant')
|
|
206
|
+
if(value.role==='assistant'){
|
|
207
|
+
allowed.add('reasoning_content');
|
|
208
|
+
allowed.add('tool_calls');
|
|
209
|
+
}
|
|
136
210
|
if(value.role==='tool') allowed.add('tool_call_id');
|
|
137
211
|
assertMessageKeys(value,allowed,label);
|
|
138
212
|
const toolCalls=value.role==='assistant'
|
|
139
|
-
?normalizeToolCalls(value.tool_calls,`${label}.tool_calls
|
|
213
|
+
?normalizeToolCalls(value.tool_calls,`${label}.tool_calls`)
|
|
140
214
|
:null;
|
|
215
|
+
if(
|
|
216
|
+
value.role==='assistant'
|
|
217
|
+
&&Object.hasOwn(value,'reasoning_content')
|
|
218
|
+
&&typeof value.reasoning_content!=='string'
|
|
219
|
+
){
|
|
220
|
+
throw new TypeError(`${label}.reasoning_content must be a string when provided.`);
|
|
221
|
+
}
|
|
222
|
+
const hasReasoning=Boolean(
|
|
223
|
+
value.role==='assistant'
|
|
224
|
+
&&typeof value.reasoning_content==='string'
|
|
225
|
+
&&value.reasoning_content.length
|
|
226
|
+
);
|
|
141
227
|
let content;
|
|
142
|
-
if(value.role==='assistant'&&toolCalls){
|
|
228
|
+
if(value.role==='assistant'&&(toolCalls||hasReasoning)){
|
|
143
229
|
if(value.content===undefined||value.content===null||value.content==='') content='';
|
|
144
|
-
else content=
|
|
230
|
+
else content=contentText(value.content,`${label}.content`);
|
|
145
231
|
}else{
|
|
146
|
-
content=
|
|
232
|
+
content=contentText(value.content,`${label}.content`);
|
|
147
233
|
}
|
|
148
234
|
let toolCallId=null;
|
|
149
235
|
if(value.role==='tool'){
|
|
150
|
-
toolCallId=
|
|
236
|
+
toolCallId=contentText(value.tool_call_id,`${label}.tool_call_id`);
|
|
151
237
|
}
|
|
152
|
-
const normalized=
|
|
238
|
+
const normalized={
|
|
153
239
|
role:value.role,
|
|
154
240
|
content,
|
|
241
|
+
...(value.role==='assistant'&&Object.hasOwn(value,'reasoning_content')
|
|
242
|
+
?{reasoning_content:value.reasoning_content}
|
|
243
|
+
:{}),
|
|
155
244
|
...(toolCalls?{tool_calls:toolCalls}:{}),
|
|
156
245
|
...(toolCallId?{tool_call_id:toolCallId}:{}),
|
|
157
|
-
}
|
|
158
|
-
if(messageCharacters(normalized)>maxMessageCharacters){
|
|
159
|
-
throw new RangeError(`${label} exceeds ${maxMessageCharacters} characters.`);
|
|
160
|
-
}
|
|
246
|
+
};
|
|
161
247
|
return normalized;
|
|
162
248
|
}
|
|
163
249
|
|
|
164
250
|
function cloneMessage(value){
|
|
165
|
-
return
|
|
251
|
+
return {
|
|
166
252
|
role:value.role,
|
|
167
253
|
content:value.content,
|
|
254
|
+
...(Object.hasOwn(value,'reasoning_content')
|
|
255
|
+
?{reasoning_content:value.reasoning_content}
|
|
256
|
+
:{}),
|
|
168
257
|
...(value.tool_calls?{
|
|
169
|
-
tool_calls:
|
|
170
|
-
function:
|
|
258
|
+
tool_calls:value.tool_calls.map(call=>({
|
|
259
|
+
function:{...call.function},
|
|
171
260
|
id:call.id,
|
|
172
261
|
type:call.type,
|
|
173
|
-
}))
|
|
262
|
+
}))
|
|
174
263
|
}:{}),
|
|
175
264
|
...(value.tool_call_id?{tool_call_id:value.tool_call_id}:{}),
|
|
176
|
-
}
|
|
265
|
+
};
|
|
177
266
|
}
|
|
178
267
|
|
|
179
268
|
function publicMessage(value){
|
|
180
269
|
return {
|
|
181
270
|
role:value.role,
|
|
182
271
|
content:value.content,
|
|
272
|
+
...(Object.hasOwn(value,'reasoning_content')
|
|
273
|
+
?{reasoning_content:value.reasoning_content}
|
|
274
|
+
:{}),
|
|
183
275
|
...(value.tool_calls?{tool_calls:value.tool_calls.map(call=>({
|
|
184
276
|
function:{...call.function},
|
|
185
277
|
id:call.id,
|
|
@@ -189,13 +281,12 @@ function publicMessage(value){
|
|
|
189
281
|
};
|
|
190
282
|
}
|
|
191
283
|
|
|
192
|
-
function normalizeInitialMessages(value
|
|
284
|
+
function normalizeInitialMessages(value){
|
|
193
285
|
if(value===undefined) return [];
|
|
194
286
|
if(!Array.isArray(value)) throw new TypeError('initialMessages must be an array.');
|
|
195
287
|
const messages=value.map((item,index)=>normalizeMessage(
|
|
196
288
|
item,
|
|
197
289
|
`initialMessages[${index}]`,
|
|
198
|
-
maxMessageCharacters,
|
|
199
290
|
new Set(['assistant','tool','user']),
|
|
200
291
|
));
|
|
201
292
|
pendingToolCallIds(messages,true);
|
|
@@ -203,41 +294,17 @@ function normalizeInitialMessages(value,maxMessageCharacters){
|
|
|
203
294
|
}
|
|
204
295
|
|
|
205
296
|
function message(role,content){
|
|
206
|
-
return
|
|
297
|
+
return {role,content};
|
|
207
298
|
}
|
|
208
299
|
|
|
209
300
|
function snapshot(messages){
|
|
210
|
-
return
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function exceedsLimits(systemPrompt,conversation,maxMessages,maxContextCharacters){
|
|
214
|
-
const count=conversation.length+(systemPrompt?1:0);
|
|
215
|
-
const characters=conversation.reduce((sum,item)=>sum+messageCharacters(item),systemPrompt?.length||0);
|
|
216
|
-
return count>maxMessages||characters>maxContextCharacters;
|
|
301
|
+
return messages.map(cloneMessage);
|
|
217
302
|
}
|
|
218
303
|
|
|
219
|
-
function
|
|
220
|
-
const bounded=conversation.map(cloneMessage);
|
|
221
|
-
while(exceedsLimits(systemPrompt,bounded,limits.maxMessages,limits.maxContextCharacters)){
|
|
222
|
-
const removable=bounded.length-minimumTail;
|
|
223
|
-
if(removable<1){
|
|
224
|
-
throw coded(
|
|
225
|
-
new RangeError('The current system prompt and message exceed the configured chat context limit.'),
|
|
226
|
-
'AI_CHAT_CONTEXT_LIMIT',
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
let removeCount=removable;
|
|
230
|
-
for(let index=1;index<removable;index++){
|
|
231
|
-
if(bounded[index].role==='user'){
|
|
232
|
-
removeCount=index;
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
bounded.splice(0,removeCount);
|
|
237
|
-
}
|
|
304
|
+
function completeHistory(systemPrompt,conversation){
|
|
238
305
|
return [
|
|
239
306
|
...(systemPrompt?[message('system',systemPrompt)]:[]),
|
|
240
|
-
...
|
|
307
|
+
...conversation.map(cloneMessage),
|
|
241
308
|
];
|
|
242
309
|
}
|
|
243
310
|
|
|
@@ -255,15 +322,26 @@ function pendingToolCallIds(messages,validate=false){
|
|
|
255
322
|
for(let index=0;index<messages.length;index++){
|
|
256
323
|
const item=messages[index];
|
|
257
324
|
if(item.role==='user'&&pending.size&&validate){
|
|
258
|
-
throw
|
|
325
|
+
throw coded(
|
|
326
|
+
new TypeError(`Message ${index+1} starts a user turn before the pending tool result.`),
|
|
327
|
+
'AI_CHAT_INCOHERENT_PERSISTENCE',
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if(item.role==='assistant'&&pending.size&&validate){
|
|
331
|
+
throw coded(
|
|
332
|
+
new TypeError(`Message ${index+1} precedes the pending tool result.`),
|
|
333
|
+
'AI_CHAT_INCOHERENT_PERSISTENCE',
|
|
334
|
+
);
|
|
259
335
|
}
|
|
260
336
|
if(item.role==='assistant'&&item.tool_calls){
|
|
261
|
-
if(pending.size&&validate) throw new TypeError(`Message ${index+1} overlaps a pending tool call.`);
|
|
262
337
|
pending.add(item.tool_calls[0].id);
|
|
263
338
|
}
|
|
264
339
|
if(item.role==='tool'){
|
|
265
340
|
if((pending.size!==1||!pending.has(item.tool_call_id))&&validate){
|
|
266
|
-
throw
|
|
341
|
+
throw coded(
|
|
342
|
+
new TypeError(`Message ${index+1} does not match the pending assistant tool call.`),
|
|
343
|
+
'AI_CHAT_INCOHERENT_PERSISTENCE',
|
|
344
|
+
);
|
|
267
345
|
}
|
|
268
346
|
pending.delete(item.tool_call_id);
|
|
269
347
|
}
|
|
@@ -282,7 +360,7 @@ async function configuredArcaneChat(request){
|
|
|
282
360
|
return api.chat(request);
|
|
283
361
|
}
|
|
284
362
|
|
|
285
|
-
function normalizeAssistantResponseMessage(value,
|
|
363
|
+
function normalizeAssistantResponseMessage(value,{requireRole=false}={}){
|
|
286
364
|
if(!isPlainRecord(value)){
|
|
287
365
|
throw coded(
|
|
288
366
|
new TypeError('The chat provider returned an invalid response.'),
|
|
@@ -301,7 +379,6 @@ function normalizeAssistantResponseMessage(value,maxMessageCharacters,{requireRo
|
|
|
301
379
|
responseMessage=normalizeMessage(
|
|
302
380
|
{...value,role:'assistant'},
|
|
303
381
|
'The assistant message',
|
|
304
|
-
maxMessageCharacters,
|
|
305
382
|
new Set(['assistant']),
|
|
306
383
|
);
|
|
307
384
|
}catch(error){
|
|
@@ -310,37 +387,60 @@ function normalizeAssistantResponseMessage(value,maxMessageCharacters,{requireRo
|
|
|
310
387
|
return responseMessage;
|
|
311
388
|
}
|
|
312
389
|
|
|
313
|
-
function normalizeSessionResponse(response
|
|
314
|
-
const responseMessage=normalizeAssistantResponseMessage(
|
|
315
|
-
response.message,
|
|
316
|
-
maxMessageCharacters,
|
|
317
|
-
);
|
|
390
|
+
function normalizeSessionResponse(response){
|
|
391
|
+
const responseMessage=normalizeAssistantResponseMessage(response.message);
|
|
318
392
|
|
|
319
|
-
return
|
|
320
|
-
provider:optionalMetadata(response.provider,'The provider name'
|
|
321
|
-
model:optionalMetadata(response.model,'The model name'
|
|
393
|
+
return {
|
|
394
|
+
provider:optionalMetadata(response.provider,'The provider name'),
|
|
395
|
+
model:optionalMetadata(response.model,'The model name'),
|
|
322
396
|
message:responseMessage,
|
|
397
|
+
providerResponse:response,
|
|
323
398
|
done:response.done===undefined?true:Boolean(response.done),
|
|
324
|
-
doneReason:optionalMetadata(response.doneReason,'The completion reason'
|
|
399
|
+
doneReason:optionalMetadata(response.doneReason,'The completion reason'),
|
|
325
400
|
promptEvalCount:usageCount(response.promptEvalCount),
|
|
326
401
|
evalCount:usageCount(response.evalCount),
|
|
327
|
-
}
|
|
402
|
+
};
|
|
328
403
|
}
|
|
329
404
|
|
|
330
|
-
function normalizeOpenAICompatibleResponse(response
|
|
331
|
-
if(!Array.isArray(response.choices)||response.choices.length
|
|
405
|
+
function normalizeOpenAICompatibleResponse(response){
|
|
406
|
+
if(!Array.isArray(response.choices)||response.choices.length===0){
|
|
332
407
|
throw coded(
|
|
333
|
-
new TypeError('The chat provider completion must contain
|
|
408
|
+
new TypeError('The chat provider completion must contain at least one choice.'),
|
|
334
409
|
'AI_CHAT_INVALID_RESPONSE',
|
|
335
410
|
);
|
|
336
411
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
412
|
+
let responseMessage=null;
|
|
413
|
+
let toolCallCount=0;
|
|
414
|
+
for(let index=0;index<response.choices.length;index++){
|
|
415
|
+
const candidate=response.choices[index];
|
|
416
|
+
if(!isPlainRecord(candidate)){
|
|
417
|
+
throw coded(
|
|
418
|
+
new TypeError('The chat provider completion contains an invalid choice.'),
|
|
419
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
const candidateMessage=normalizeAssistantResponseMessage(
|
|
423
|
+
candidate.message,
|
|
424
|
+
{requireRole:true},
|
|
342
425
|
);
|
|
426
|
+
toolCallCount+=candidateMessage.tool_calls?.length??0;
|
|
427
|
+
if(toolCallCount>1){
|
|
428
|
+
throw coded(
|
|
429
|
+
new TypeError('The chat provider completion contains parallel structural tool calls.'),
|
|
430
|
+
'AI_CHAT_PARALLEL_TOOLS_UNSUPPORTED',
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
if(index>0&&candidateMessage.tool_calls?.length){
|
|
434
|
+
throw coded(
|
|
435
|
+
new TypeError(
|
|
436
|
+
'The chat provider completion placed a structural tool call outside the selected choice.'
|
|
437
|
+
),
|
|
438
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
if(index===0) responseMessage=candidateMessage;
|
|
343
442
|
}
|
|
443
|
+
const choice=response.choices[0];
|
|
344
444
|
if(response.usage!==undefined&&response.usage!==null&&!isPlainRecord(response.usage)){
|
|
345
445
|
throw coded(
|
|
346
446
|
new TypeError('The chat provider completion usage must be a plain object when provided.'),
|
|
@@ -348,18 +448,13 @@ function normalizeOpenAICompatibleResponse(response,maxMessageCharacters){
|
|
|
348
448
|
);
|
|
349
449
|
}
|
|
350
450
|
const usage=response.usage??{};
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
{requireRole:true},
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
return Object.freeze({
|
|
358
|
-
provider:optionalMetadata(response.provider,'The provider name',128),
|
|
359
|
-
model:optionalMetadata(response.model,'The model name',256),
|
|
451
|
+
return {
|
|
452
|
+
provider:optionalMetadata(response.provider,'The provider name'),
|
|
453
|
+
model:optionalMetadata(response.model,'The model name'),
|
|
360
454
|
message:responseMessage,
|
|
455
|
+
providerResponse:response,
|
|
361
456
|
done:true,
|
|
362
|
-
doneReason:optionalMetadata(choice.finish_reason,'The completion reason'
|
|
457
|
+
doneReason:optionalMetadata(choice.finish_reason,'The completion reason'),
|
|
363
458
|
promptEvalCount:providerUsageCount(
|
|
364
459
|
usage.prompt_tokens,
|
|
365
460
|
'The provider prompt token count',
|
|
@@ -368,38 +463,44 @@ function normalizeOpenAICompatibleResponse(response,maxMessageCharacters){
|
|
|
368
463
|
usage.completion_tokens,
|
|
369
464
|
'The provider completion token count',
|
|
370
465
|
),
|
|
371
|
-
}
|
|
466
|
+
};
|
|
372
467
|
}
|
|
373
468
|
|
|
374
|
-
function normalizeResponse(response
|
|
469
|
+
function normalizeResponse(response){
|
|
375
470
|
if(!isPlainRecord(response)){
|
|
376
471
|
throw coded(
|
|
377
472
|
new TypeError('The chat provider returned an invalid response.'),
|
|
378
473
|
'AI_CHAT_INVALID_RESPONSE',
|
|
379
474
|
);
|
|
380
475
|
}
|
|
381
|
-
|
|
382
|
-
|
|
476
|
+
const hasMessage=Object.hasOwn(response,'message');
|
|
477
|
+
const hasChoices=Object.hasOwn(response,'choices');
|
|
478
|
+
if(hasMessage&&hasChoices){
|
|
479
|
+
throw coded(
|
|
480
|
+
new TypeError('The chat provider response cannot mix message and choices envelopes.'),
|
|
481
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
if(hasMessage){
|
|
485
|
+
return normalizeSessionResponse(response);
|
|
383
486
|
}
|
|
384
|
-
return normalizeOpenAICompatibleResponse(response
|
|
487
|
+
return normalizeOpenAICompatibleResponse(response);
|
|
385
488
|
}
|
|
386
489
|
|
|
387
490
|
/**
|
|
388
|
-
* Maintains one
|
|
491
|
+
* Maintains one complete, in-memory conversation through a configured chat provider.
|
|
389
492
|
*
|
|
390
493
|
* This module performs no persistence, streaming, tool execution, rendering, or
|
|
391
|
-
* provider selection. Applications own their prompt policy and may
|
|
494
|
+
* provider selection. Applications own their prompt policy and may supply an
|
|
392
495
|
* asynchronous contextBuilder that returns additional system text for each send.
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
* non-stream OpenAI-compatible completion containing exactly one choice.
|
|
496
|
+
* Legacy response-length options are accepted without changing or shortening
|
|
497
|
+
* the caller's prompt. A configured chat function may return either the normalized
|
|
498
|
+
* session response or a non-stream OpenAI-compatible completion.
|
|
397
499
|
*/
|
|
398
500
|
export default class ConfiguredAIChatSession{
|
|
399
501
|
#chat;
|
|
400
502
|
#contextBuilder;
|
|
401
503
|
#conversation=[];
|
|
402
|
-
#limits;
|
|
403
504
|
#pending=false;
|
|
404
505
|
#request;
|
|
405
506
|
#systemPrompt;
|
|
@@ -422,53 +523,24 @@ export default class ConfiguredAIChatSession{
|
|
|
422
523
|
|
|
423
524
|
const chat=options.chat===undefined?configuredArcaneChat:options.chat;
|
|
424
525
|
const contextBuilder=options.contextBuilder??null;
|
|
425
|
-
const request=options.request
|
|
526
|
+
const request=normalizeRequestOptions(options.request,'request');
|
|
426
527
|
if(typeof chat!=='function') throw new TypeError('chat must be a function.');
|
|
427
528
|
if(contextBuilder!==null&&typeof contextBuilder!=='function'){
|
|
428
529
|
throw new TypeError('contextBuilder must be a function when provided.');
|
|
429
530
|
}
|
|
430
|
-
if(!isPlainRecord(request)) throw new TypeError('request must be a plain object.');
|
|
431
|
-
const forbidden=Object.keys(request).find(key=>FORBIDDEN_REQUEST_FIELDS.has(key));
|
|
432
|
-
if(forbidden) throw new TypeError(`request.${forbidden} is managed by the chat session.`);
|
|
433
|
-
|
|
434
|
-
const maxMessages=boundedInteger(
|
|
435
|
-
options.maxMessages??DEFAULT_MAX_MESSAGES,
|
|
436
|
-
'maxMessages',
|
|
437
|
-
{minimum:3,maximum:128},
|
|
438
|
-
);
|
|
439
|
-
const maxMessageCharacters=boundedInteger(
|
|
440
|
-
options.maxMessageCharacters??DEFAULT_MAX_MESSAGE_CHARACTERS,
|
|
441
|
-
'maxMessageCharacters',
|
|
442
|
-
{minimum:1,maximum:131072},
|
|
443
|
-
);
|
|
444
|
-
const maxContextCharacters=boundedInteger(
|
|
445
|
-
options.maxContextCharacters??DEFAULT_MAX_CONTEXT_CHARACTERS,
|
|
446
|
-
'maxContextCharacters',
|
|
447
|
-
{minimum:1,maximum:MAX_PROVIDER_CONTEXT_CHARACTERS},
|
|
448
|
-
);
|
|
449
531
|
const rawSystemPrompt=options.systemPrompt??'';
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
:rawSystemPrompt;
|
|
453
|
-
const systemPrompt=boundedContent(
|
|
454
|
-
configuredSystemPrompt,
|
|
532
|
+
const systemPrompt=contentText(
|
|
533
|
+
rawSystemPrompt,
|
|
455
534
|
'systemPrompt',
|
|
456
|
-
maxMessageCharacters,
|
|
457
535
|
{optional:true},
|
|
458
536
|
);
|
|
459
537
|
|
|
460
538
|
this.#chat=chat;
|
|
461
539
|
this.#contextBuilder=contextBuilder;
|
|
462
|
-
this.#
|
|
463
|
-
this.#request=Object.freeze({...request});
|
|
540
|
+
this.#request={...request};
|
|
464
541
|
this.#systemPrompt=systemPrompt;
|
|
465
|
-
const initialMessages=normalizeInitialMessages(options.initialMessages
|
|
466
|
-
const initialHistory=
|
|
467
|
-
this.#systemPrompt,
|
|
468
|
-
initialMessages,
|
|
469
|
-
this.#limits,
|
|
470
|
-
0,
|
|
471
|
-
);
|
|
542
|
+
const initialMessages=normalizeInitialMessages(options.initialMessages);
|
|
543
|
+
const initialHistory=completeHistory(this.#systemPrompt,initialMessages);
|
|
472
544
|
this.#conversation=initialHistory.filter(item=>item.role!=='system');
|
|
473
545
|
}
|
|
474
546
|
|
|
@@ -490,27 +562,18 @@ export default class ConfiguredAIChatSession{
|
|
|
490
562
|
async #contextFor(input,signal){
|
|
491
563
|
let context=null;
|
|
492
564
|
if(this.#contextBuilder){
|
|
493
|
-
const value=await this.#contextBuilder(
|
|
565
|
+
const value=await this.#contextBuilder({
|
|
494
566
|
input,
|
|
495
567
|
history:this.history(),
|
|
496
568
|
signal:signal??null,
|
|
497
|
-
})
|
|
569
|
+
});
|
|
498
570
|
if(value!==undefined&&value!==null){
|
|
499
|
-
const raw=
|
|
571
|
+
const raw=contentText(
|
|
500
572
|
value,
|
|
501
573
|
'The contextBuilder result',
|
|
502
|
-
this.#limits.maxMessageCharacters,
|
|
503
574
|
{optional:true},
|
|
504
575
|
);
|
|
505
|
-
if(raw)
|
|
506
|
-
context=CONTEXT_PREFIX+raw;
|
|
507
|
-
if(context.length>this.#limits.maxMessageCharacters){
|
|
508
|
-
throw coded(
|
|
509
|
-
new RangeError('The contextBuilder result exceeds the per-message limit after its safety prefix.'),
|
|
510
|
-
'AI_CHAT_CONTEXT_LIMIT',
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
576
|
+
if(raw) context=raw;
|
|
514
577
|
}
|
|
515
578
|
}
|
|
516
579
|
return context;
|
|
@@ -518,21 +581,20 @@ export default class ConfiguredAIChatSession{
|
|
|
518
581
|
|
|
519
582
|
async prepare(input,options={}){
|
|
520
583
|
if(!isPlainRecord(options)) throw new TypeError('Chat send options must be a plain object.');
|
|
521
|
-
const unsupported=Object.keys(options).find(key
|
|
584
|
+
const unsupported=Object.keys(options).find(key=>!['request','signal'].includes(key));
|
|
522
585
|
if(unsupported) throw new TypeError(`Unsupported chat send option: ${unsupported}`);
|
|
523
586
|
if(!signalLike(options.signal)) throw new TypeError('signal must be an AbortSignal.');
|
|
524
587
|
if(options.signal?.aborted) throw abortError();
|
|
588
|
+
const turnRequest=normalizeRequestOptions(options.request,'request');
|
|
525
589
|
const inputMessage=typeof input==='string'
|
|
526
590
|
?normalizeMessage(
|
|
527
591
|
{role:'user',content:input},
|
|
528
592
|
'The user message',
|
|
529
|
-
this.#limits.maxMessageCharacters,
|
|
530
593
|
new Set(['user']),
|
|
531
594
|
)
|
|
532
595
|
:normalizeMessage(
|
|
533
596
|
input,
|
|
534
597
|
'The request message',
|
|
535
|
-
this.#limits.maxMessageCharacters,
|
|
536
598
|
new Set(['tool','user']),
|
|
537
599
|
);
|
|
538
600
|
if(this.#pending){
|
|
@@ -567,50 +629,39 @@ export default class ConfiguredAIChatSession{
|
|
|
567
629
|
const transientContext=context
|
|
568
630
|
?message('user',context)
|
|
569
631
|
:null;
|
|
570
|
-
const
|
|
571
|
-
const requestMessages=
|
|
632
|
+
const transientMessages=[...(transientContext?[transientContext]:[]),inputMessage];
|
|
633
|
+
const requestMessages=completeHistory(
|
|
572
634
|
this.#systemPrompt,
|
|
573
|
-
[...this.#conversation,...
|
|
574
|
-
this.#limits,
|
|
575
|
-
inputMessage.role==='tool'
|
|
576
|
-
?this.#conversation.length-toolCallIndex+transientTail.length
|
|
577
|
-
:transientTail.length,
|
|
635
|
+
[...this.#conversation,...transientMessages],
|
|
578
636
|
);
|
|
579
637
|
let providerResponse;
|
|
580
638
|
try{
|
|
581
|
-
|
|
639
|
+
const providerRequest={
|
|
582
640
|
...this.#request,
|
|
641
|
+
...turnRequest,
|
|
583
642
|
...(options.signal?{signal:options.signal}:{}),
|
|
584
643
|
messages:requestMessages.map(publicMessage),
|
|
585
|
-
}
|
|
644
|
+
};
|
|
645
|
+
if(
|
|
646
|
+
providerRequest.tools?.length
|
|
647
|
+
&&providerRequest.parallelToolCalls===undefined
|
|
648
|
+
&&providerRequest.parallel_tool_calls===undefined
|
|
649
|
+
) providerRequest.parallelToolCalls=false;
|
|
650
|
+
providerResponse=await this.#chat(providerRequest);
|
|
586
651
|
}catch(error){
|
|
587
652
|
if(options.signal?.aborted) throw abortError();
|
|
588
653
|
throw error;
|
|
589
654
|
}
|
|
590
|
-
const response=normalizeResponse(
|
|
591
|
-
providerResponse,
|
|
592
|
-
this.#limits.maxMessageCharacters,
|
|
593
|
-
);
|
|
655
|
+
const response=normalizeResponse(providerResponse);
|
|
594
656
|
if(options.signal?.aborted) throw abortError();
|
|
595
|
-
const
|
|
596
|
-
const
|
|
597
|
-
systemOffset,
|
|
598
|
-
requestMessages.length-transientTail.length,
|
|
599
|
-
);
|
|
600
|
-
const retainedToolCallIndex=inputMessage.role==='tool'
|
|
601
|
-
?matchingToolCallIndex(retainedConversation,inputMessage.tool_call_id)
|
|
602
|
-
:-1;
|
|
603
|
-
const committed=boundedHistory(
|
|
657
|
+
const retainedConversation=this.#conversation.map(cloneMessage);
|
|
658
|
+
const committed=completeHistory(
|
|
604
659
|
this.#systemPrompt,
|
|
605
660
|
[...retainedConversation,inputMessage,response.message],
|
|
606
|
-
this.#limits,
|
|
607
|
-
inputMessage.role==='tool'
|
|
608
|
-
?retainedConversation.length-retainedToolCallIndex+2
|
|
609
|
-
:2,
|
|
610
661
|
);
|
|
611
662
|
const nextConversation=committed.filter(item=>item.role!=='system');
|
|
612
663
|
let settled=false;
|
|
613
|
-
return
|
|
664
|
+
return {
|
|
614
665
|
response,
|
|
615
666
|
commit:()=>{
|
|
616
667
|
if(settled) throw coded(new Error('The prepared chat turn is already settled.'),'AI_CHAT_TRANSACTION_SETTLED');
|
|
@@ -625,7 +676,7 @@ export default class ConfiguredAIChatSession{
|
|
|
625
676
|
this.#pending=false;
|
|
626
677
|
return true;
|
|
627
678
|
},
|
|
628
|
-
}
|
|
679
|
+
};
|
|
629
680
|
}catch(error){
|
|
630
681
|
this.#pending=false;
|
|
631
682
|
throw error;
|