@openrouter/sdk 0.3.14 → 0.3.15

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 (129) hide show
  1. package/esm/funcs/analyticsGetUserActivity.d.ts +1 -1
  2. package/esm/funcs/analyticsGetUserActivity.js +1 -1
  3. package/esm/funcs/apiKeysCreate.d.ts +3 -0
  4. package/esm/funcs/apiKeysCreate.js +3 -0
  5. package/esm/funcs/apiKeysDelete.d.ts +3 -0
  6. package/esm/funcs/apiKeysDelete.js +3 -0
  7. package/esm/funcs/apiKeysGet.d.ts +3 -0
  8. package/esm/funcs/apiKeysGet.js +3 -0
  9. package/esm/funcs/apiKeysList.d.ts +3 -0
  10. package/esm/funcs/apiKeysList.js +3 -0
  11. package/esm/funcs/apiKeysUpdate.d.ts +3 -0
  12. package/esm/funcs/apiKeysUpdate.js +3 -0
  13. package/esm/funcs/call-model.js +9 -6
  14. package/esm/funcs/creditsGetCredits.d.ts +1 -1
  15. package/esm/funcs/creditsGetCredits.js +1 -1
  16. package/esm/funcs/guardrailsBulkAssignKeys.d.ts +18 -0
  17. package/esm/funcs/guardrailsBulkAssignKeys.js +89 -0
  18. package/esm/funcs/guardrailsBulkAssignMembers.d.ts +18 -0
  19. package/esm/funcs/guardrailsBulkAssignMembers.js +89 -0
  20. package/esm/funcs/guardrailsBulkUnassignKeys.d.ts +18 -0
  21. package/esm/funcs/guardrailsBulkUnassignKeys.js +89 -0
  22. package/esm/funcs/guardrailsBulkUnassignMembers.d.ts +18 -0
  23. package/esm/funcs/guardrailsBulkUnassignMembers.js +89 -0
  24. package/esm/funcs/guardrailsCreate.d.ts +18 -0
  25. package/esm/funcs/guardrailsCreate.js +83 -0
  26. package/esm/funcs/guardrailsDelete.d.ts +18 -0
  27. package/esm/funcs/guardrailsDelete.js +88 -0
  28. package/esm/funcs/{parametersGetParameters.d.ts → guardrailsGet.d.ts} +6 -3
  29. package/esm/funcs/guardrailsGet.js +88 -0
  30. package/esm/funcs/guardrailsList.d.ts +18 -0
  31. package/esm/funcs/guardrailsList.js +87 -0
  32. package/esm/funcs/guardrailsListGuardrailKeyAssignments.d.ts +18 -0
  33. package/esm/funcs/guardrailsListGuardrailKeyAssignments.js +93 -0
  34. package/esm/funcs/guardrailsListGuardrailMemberAssignments.d.ts +18 -0
  35. package/esm/funcs/guardrailsListGuardrailMemberAssignments.js +93 -0
  36. package/esm/funcs/guardrailsListKeyAssignments.d.ts +18 -0
  37. package/esm/funcs/guardrailsListKeyAssignments.js +87 -0
  38. package/esm/funcs/guardrailsListMemberAssignments.d.ts +18 -0
  39. package/esm/funcs/guardrailsListMemberAssignments.js +87 -0
  40. package/esm/funcs/guardrailsUpdate.d.ts +18 -0
  41. package/esm/funcs/{parametersGetParameters.js → guardrailsUpdate.js} +24 -32
  42. package/esm/index.d.ts +4 -3
  43. package/esm/index.js +3 -1
  44. package/esm/lib/async-params.d.ts +46 -6
  45. package/esm/lib/async-params.js +10 -2
  46. package/esm/lib/config.d.ts +2 -4
  47. package/esm/lib/config.js +2 -2
  48. package/esm/lib/conversation-state.d.ts +61 -0
  49. package/esm/lib/conversation-state.js +207 -0
  50. package/esm/lib/model-result.d.ts +175 -2
  51. package/esm/lib/model-result.js +678 -181
  52. package/esm/lib/tool-types.d.ts +108 -0
  53. package/esm/lib/tool-types.js +13 -0
  54. package/esm/lib/tool.d.ts +21 -1
  55. package/esm/lib/tool.js +7 -0
  56. package/esm/models/assistantmessage.d.ts +31 -0
  57. package/esm/models/assistantmessage.js +43 -0
  58. package/esm/models/chatmessagecontentitemimage.d.ts +8 -8
  59. package/esm/models/chatmessagecontentitemimage.js +8 -9
  60. package/esm/models/chatresponsechoice.d.ts +0 -2
  61. package/esm/models/chatresponsechoice.js +0 -3
  62. package/esm/models/chatstreamingmessagechunk.d.ts +2 -2
  63. package/esm/models/chatstreamingmessagechunk.js +2 -2
  64. package/esm/models/index.d.ts +1 -1
  65. package/esm/models/index.js +1 -1
  66. package/esm/models/model.d.ts +4 -0
  67. package/esm/models/model.js +2 -0
  68. package/esm/models/operations/bulkassignkeystoguardrail.d.ts +44 -0
  69. package/esm/models/operations/bulkassignkeystoguardrail.js +42 -0
  70. package/esm/models/operations/bulkassignmemberstoguardrail.d.ts +44 -0
  71. package/esm/models/operations/bulkassignmemberstoguardrail.js +42 -0
  72. package/esm/models/operations/bulkunassignkeysfromguardrail.d.ts +44 -0
  73. package/esm/models/operations/bulkunassignkeysfromguardrail.js +42 -0
  74. package/esm/models/operations/bulkunassignmembersfromguardrail.d.ts +44 -0
  75. package/esm/models/operations/bulkunassignmembersfromguardrail.js +42 -0
  76. package/esm/models/operations/createguardrail.d.ts +136 -0
  77. package/esm/models/operations/createguardrail.js +85 -0
  78. package/esm/models/operations/deleteguardrail.d.ts +29 -0
  79. package/esm/models/operations/deleteguardrail.js +21 -0
  80. package/esm/models/operations/getguardrail.d.ts +92 -0
  81. package/esm/models/operations/getguardrail.js +60 -0
  82. package/esm/models/operations/getmodels.d.ts +28 -1
  83. package/esm/models/operations/getmodels.js +22 -1
  84. package/esm/models/operations/index.d.ts +13 -1
  85. package/esm/models/operations/index.js +13 -1
  86. package/esm/models/operations/listguardrailkeyassignments.d.ts +76 -0
  87. package/esm/models/operations/listguardrailkeyassignments.js +51 -0
  88. package/esm/models/operations/listguardrailmemberassignments.d.ts +72 -0
  89. package/esm/models/operations/listguardrailmemberassignments.js +49 -0
  90. package/esm/models/operations/listguardrails.d.ts +98 -0
  91. package/esm/models/operations/listguardrails.js +66 -0
  92. package/esm/models/operations/listkeyassignments.d.ts +71 -0
  93. package/esm/models/operations/listkeyassignments.js +50 -0
  94. package/esm/models/operations/listmemberassignments.d.ts +67 -0
  95. package/esm/models/operations/listmemberassignments.js +48 -0
  96. package/esm/models/operations/updateguardrail.d.ts +151 -0
  97. package/esm/models/operations/updateguardrail.js +97 -0
  98. package/esm/models/percentilelatencycutoffs.js +1 -1
  99. package/esm/models/percentilestats.js +1 -1
  100. package/esm/models/percentilethroughputcutoffs.js +1 -1
  101. package/esm/models/preferredmaxlatency.js +1 -1
  102. package/esm/models/preferredminthroughput.js +1 -1
  103. package/esm/models/responseinputvideo.js +1 -1
  104. package/esm/models/responsesoutputmodality.js +1 -1
  105. package/esm/models/schema2.d.ts +92 -0
  106. package/esm/models/schema2.js +109 -0
  107. package/esm/sdk/analytics.d.ts +1 -1
  108. package/esm/sdk/analytics.js +1 -1
  109. package/esm/sdk/apikeys.d.ts +15 -0
  110. package/esm/sdk/apikeys.js +15 -0
  111. package/esm/sdk/credits.d.ts +1 -1
  112. package/esm/sdk/credits.js +1 -1
  113. package/esm/sdk/guardrails.d.ts +96 -0
  114. package/esm/sdk/guardrails.js +139 -0
  115. package/esm/sdk/sdk.d.ts +3 -3
  116. package/esm/sdk/sdk.js +4 -4
  117. package/esm/types/index.d.ts +2 -0
  118. package/esm/types/index.js +1 -0
  119. package/esm/types/models.d.ts +25 -0
  120. package/esm/types/models.js +10 -0
  121. package/jsr.json +1 -1
  122. package/package.json +12 -10
  123. package/scripts/check-types.js +127 -0
  124. package/esm/models/operations/getparameters.d.ts +0 -87
  125. package/esm/models/operations/getparameters.js +0 -73
  126. package/esm/models/schema3.d.ts +0 -51
  127. package/esm/models/schema3.js +0 -62
  128. package/esm/sdk/parameters.d.ts +0 -9
  129. package/esm/sdk/parameters.js +0 -16
@@ -1,20 +1,34 @@
1
1
  import { ToolEventBroadcaster } from './tool-event-broadcaster.js';
2
2
  import { betaResponsesSend } from '../funcs/betaResponsesSend.js';
3
3
  import { hasAsyncFunctions, resolveAsyncFunctions, } from './async-params.js';
4
+ import { appendToMessages, createInitialState, createRejectedResult, createUnsentResult, extractTextFromResponse as extractTextFromResponseState, partitionToolCalls, unsentResultsToAPIFormat, updateState, } from './conversation-state.js';
4
5
  import { ReusableReadableStream } from './reusable-stream.js';
5
6
  import { buildResponsesMessageStream, buildToolCallStream, consumeStreamForCompletion, extractReasoningDeltas, extractResponsesMessageFromResponse, extractTextDeltas, extractTextFromResponse, extractToolCallsFromResponse, extractToolDeltas, } from './stream-transformers.js';
6
7
  import { executeTool } from './tool-executor.js';
7
8
  import { executeNextTurnParamsFunctions, applyNextTurnParamsToRequest } from './next-turn-params.js';
8
9
  import { hasExecuteFunction } from './tool-types.js';
9
- import { isStopConditionMet } from './stop-conditions.js';
10
+ import { isStopConditionMet, stepCountIs } from './stop-conditions.js';
11
+ /**
12
+ * Default maximum number of tool execution steps if no stopWhen is specified.
13
+ * This prevents infinite loops in tool execution.
14
+ */
15
+ const DEFAULT_MAX_STEPS = 5;
10
16
  /**
11
17
  * Type guard for stream event with toReadableStream method
18
+ * Checks constructor name, prototype, and method availability
12
19
  */
13
20
  function isEventStream(value) {
14
- return (value !== null &&
15
- typeof value === 'object' &&
16
- 'toReadableStream' in value &&
17
- typeof value.toReadableStream === 'function');
21
+ if (value === null || typeof value !== 'object') {
22
+ return false;
23
+ }
24
+ // Check constructor name for EventStream
25
+ const constructorName = Object.getPrototypeOf(value)?.constructor?.name;
26
+ if (constructorName === 'EventStream') {
27
+ return true;
28
+ }
29
+ // Fallback: check for toReadableStream method (may be on prototype)
30
+ const maybeStream = value;
31
+ return typeof maybeStream.toReadableStream === 'function';
18
32
  }
19
33
  /**
20
34
  * Type guard for output items with a type property
@@ -47,7 +61,6 @@ function hasTypeProperty(item) {
47
61
  export class ModelResult {
48
62
  constructor(options) {
49
63
  this.reusableStream = null;
50
- this.streamPromise = null;
51
64
  this.textPromise = null;
52
65
  this.initPromise = null;
53
66
  this.toolExecutionPromise = null;
@@ -56,7 +69,26 @@ export class ModelResult {
56
69
  this.allToolExecutionRounds = [];
57
70
  // Track resolved request after async function resolution
58
71
  this.resolvedRequest = null;
72
+ // State management for multi-turn conversations
73
+ this.stateAccessor = null;
74
+ this.currentState = null;
75
+ this.requireApprovalFn = null;
76
+ this.approvedToolCalls = [];
77
+ this.rejectedToolCalls = [];
78
+ this.isResumingFromApproval = false;
59
79
  this.options = options;
80
+ // Runtime validation: approval decisions require state
81
+ const hasApprovalDecisions = (options.approveToolCalls && options.approveToolCalls.length > 0) ||
82
+ (options.rejectToolCalls && options.rejectToolCalls.length > 0);
83
+ if (hasApprovalDecisions && !options.state) {
84
+ throw new Error('approveToolCalls and rejectToolCalls require a state accessor. ' +
85
+ 'Provide a StateAccessor via the "state" parameter to persist approval decisions.');
86
+ }
87
+ // Initialize state management
88
+ this.stateAccessor = options.state ?? null;
89
+ this.requireApprovalFn = options.requireApproval ?? null;
90
+ this.approvedToolCalls = options.approveToolCalls ?? [];
91
+ this.rejectedToolCalls = options.rejectToolCalls ?? [];
60
92
  }
61
93
  /**
62
94
  * Get or create the tool event broadcaster (lazy initialization).
@@ -70,15 +102,372 @@ export class ModelResult {
70
102
  }
71
103
  /**
72
104
  * Type guard to check if a value is a non-streaming response
105
+ * Only requires 'output' field and absence of 'toReadableStream' method
73
106
  */
74
107
  isNonStreamingResponse(value) {
75
108
  return (value !== null &&
76
109
  typeof value === 'object' &&
77
- 'id' in value &&
78
- 'object' in value &&
79
110
  'output' in value &&
80
111
  !('toReadableStream' in value));
81
112
  }
113
+ // =========================================================================
114
+ // Extracted Helper Methods for executeToolsIfNeeded
115
+ // =========================================================================
116
+ /**
117
+ * Get initial response from stream or cached final response.
118
+ * Consumes the stream to completion if needed to extract the response.
119
+ *
120
+ * @returns The complete non-streaming response
121
+ * @throws Error if neither stream nor response has been initialized
122
+ */
123
+ async getInitialResponse() {
124
+ if (this.finalResponse) {
125
+ return this.finalResponse;
126
+ }
127
+ if (this.reusableStream) {
128
+ return consumeStreamForCompletion(this.reusableStream);
129
+ }
130
+ throw new Error('Neither stream nor response initialized');
131
+ }
132
+ /**
133
+ * Save response output to state.
134
+ * Appends the response output to the message history and records the response ID.
135
+ *
136
+ * @param response - The API response to save
137
+ */
138
+ async saveResponseToState(response) {
139
+ if (!this.stateAccessor || !this.currentState)
140
+ return;
141
+ const outputItems = Array.isArray(response.output)
142
+ ? response.output
143
+ : [response.output];
144
+ await this.saveStateSafely({
145
+ messages: appendToMessages(this.currentState.messages, outputItems),
146
+ previousResponseId: response.id,
147
+ });
148
+ }
149
+ /**
150
+ * Mark state as complete.
151
+ * Sets the conversation status to 'complete' indicating no further tool execution is needed.
152
+ */
153
+ async markStateComplete() {
154
+ await this.saveStateSafely({ status: 'complete' });
155
+ }
156
+ /**
157
+ * Save tool results to state.
158
+ * Appends tool execution results to the message history for multi-turn context.
159
+ *
160
+ * @param toolResults - The tool execution results to save
161
+ */
162
+ async saveToolResultsToState(toolResults) {
163
+ if (!this.currentState)
164
+ return;
165
+ await this.saveStateSafely({
166
+ messages: appendToMessages(this.currentState.messages, toolResults),
167
+ });
168
+ }
169
+ /**
170
+ * Check if execution should be interrupted by external signal.
171
+ * Polls the state accessor for interruption flags set by external processes.
172
+ *
173
+ * @param currentResponse - The current response to save as partial state
174
+ * @returns True if interrupted and caller should exit, false to continue
175
+ */
176
+ async checkForInterruption(currentResponse) {
177
+ if (!this.stateAccessor)
178
+ return false;
179
+ const freshState = await this.stateAccessor.load();
180
+ if (!freshState?.interruptedBy)
181
+ return false;
182
+ // Save partial state
183
+ if (this.currentState) {
184
+ const currentToolCalls = extractToolCallsFromResponse(currentResponse);
185
+ await this.saveStateSafely({
186
+ status: 'interrupted',
187
+ partialResponse: {
188
+ text: extractTextFromResponseState(currentResponse),
189
+ toolCalls: currentToolCalls,
190
+ },
191
+ });
192
+ }
193
+ this.finalResponse = currentResponse;
194
+ return true;
195
+ }
196
+ /**
197
+ * Check if stop conditions are met.
198
+ * Returns true if execution should stop.
199
+ *
200
+ * @remarks
201
+ * Default: stepCountIs(DEFAULT_MAX_STEPS) if no stopWhen is specified.
202
+ * This evaluates stop conditions against the complete step history.
203
+ */
204
+ async shouldStopExecution() {
205
+ const stopWhen = this.options.stopWhen ?? stepCountIs(DEFAULT_MAX_STEPS);
206
+ const stopConditions = Array.isArray(stopWhen)
207
+ ? stopWhen
208
+ : [stopWhen];
209
+ return isStopConditionMet({
210
+ stopConditions,
211
+ steps: this.allToolExecutionRounds.map((round) => ({
212
+ stepType: 'continue',
213
+ text: extractTextFromResponse(round.response),
214
+ toolCalls: round.toolCalls,
215
+ toolResults: round.toolResults.map((tr) => ({
216
+ toolCallId: tr.callId,
217
+ toolName: round.toolCalls.find((tc) => tc.id === tr.callId)?.name ?? '',
218
+ result: JSON.parse(tr.output),
219
+ })),
220
+ response: round.response,
221
+ usage: round.response.usage,
222
+ finishReason: undefined,
223
+ })),
224
+ });
225
+ }
226
+ /**
227
+ * Check if any tool calls have execute functions.
228
+ * Used to determine if automatic tool execution should be attempted.
229
+ *
230
+ * @param toolCalls - The tool calls to check
231
+ * @returns True if at least one tool call has an executable function
232
+ */
233
+ hasExecutableToolCalls(toolCalls) {
234
+ return toolCalls.some((toolCall) => {
235
+ const tool = this.options.tools?.find((t) => t.function.name === toolCall.name);
236
+ return tool && hasExecuteFunction(tool);
237
+ });
238
+ }
239
+ /**
240
+ * Execute tools that can auto-execute (don't require approval).
241
+ * Processes tool calls that are approved for automatic execution.
242
+ *
243
+ * @param toolCalls - The tool calls to execute
244
+ * @param turnContext - The current turn context
245
+ * @returns Array of unsent tool results for later submission
246
+ */
247
+ async executeAutoApproveTools(toolCalls, turnContext) {
248
+ const results = [];
249
+ for (const tc of toolCalls) {
250
+ const tool = this.options.tools?.find(t => t.function.name === tc.name);
251
+ if (!tool || !hasExecuteFunction(tool))
252
+ continue;
253
+ const result = await executeTool(tool, tc, turnContext);
254
+ if (result.error) {
255
+ results.push(createRejectedResult(tc.id, String(tc.name), result.error.message));
256
+ }
257
+ else {
258
+ results.push(createUnsentResult(tc.id, String(tc.name), result.result));
259
+ }
260
+ }
261
+ return results;
262
+ }
263
+ /**
264
+ * Check for tools requiring approval and handle accordingly.
265
+ * Partitions tool calls into those needing approval and those that can auto-execute.
266
+ *
267
+ * @param toolCalls - The tool calls to check
268
+ * @param currentRound - The current execution round (1-indexed)
269
+ * @param currentResponse - The current response to save if pausing
270
+ * @returns True if execution should pause for approval, false to continue
271
+ * @throws Error if approval is required but no state accessor is configured
272
+ */
273
+ async handleApprovalCheck(toolCalls, currentRound, currentResponse) {
274
+ if (!this.options.tools)
275
+ return false;
276
+ const turnContext = { numberOfTurns: currentRound };
277
+ const { requiresApproval: needsApproval, autoExecute } = await partitionToolCalls(toolCalls, this.options.tools, turnContext, this.requireApprovalFn ?? undefined);
278
+ if (needsApproval.length === 0)
279
+ return false;
280
+ // Validate: approval requires state accessor
281
+ if (!this.stateAccessor) {
282
+ const toolNames = needsApproval.map(tc => tc.name).join(', ');
283
+ throw new Error(`Tool(s) require approval but no state accessor is configured: ${toolNames}. ` +
284
+ 'Provide a StateAccessor via the "state" parameter to enable approval workflows.');
285
+ }
286
+ // Execute auto-approve tools
287
+ const unsentResults = await this.executeAutoApproveTools(autoExecute, turnContext);
288
+ // Save state with pending approvals
289
+ const stateUpdates = {
290
+ pendingToolCalls: needsApproval,
291
+ status: 'awaiting_approval',
292
+ };
293
+ if (unsentResults.length > 0) {
294
+ stateUpdates.unsentToolResults = unsentResults;
295
+ }
296
+ await this.saveStateSafely(stateUpdates);
297
+ this.finalResponse = currentResponse;
298
+ return true; // Pause for approval
299
+ }
300
+ /**
301
+ * Execute all tools in a single round.
302
+ * Runs each tool call sequentially and collects results for API submission.
303
+ *
304
+ * @param toolCalls - The tool calls to execute
305
+ * @param turnContext - The current turn context
306
+ * @returns Array of function call outputs formatted for the API
307
+ */
308
+ async executeToolRound(toolCalls, turnContext) {
309
+ const toolResults = [];
310
+ for (const toolCall of toolCalls) {
311
+ const tool = this.options.tools?.find((t) => t.function.name === toolCall.name);
312
+ if (!tool || !hasExecuteFunction(tool))
313
+ continue;
314
+ // Create callback for real-time preliminary results
315
+ const onPreliminaryResult = this.toolEventBroadcaster
316
+ ? (callId, resultValue) => {
317
+ this.toolEventBroadcaster?.push({
318
+ type: 'preliminary_result',
319
+ toolCallId: callId,
320
+ result: resultValue,
321
+ });
322
+ }
323
+ : undefined;
324
+ const result = await executeTool(tool, toolCall, turnContext, onPreliminaryResult);
325
+ toolResults.push({
326
+ type: 'function_call_output',
327
+ id: `output_${toolCall.id}`,
328
+ callId: toolCall.id,
329
+ output: result.error
330
+ ? JSON.stringify({ error: result.error.message })
331
+ : JSON.stringify(result.result),
332
+ });
333
+ }
334
+ return toolResults;
335
+ }
336
+ /**
337
+ * Resolve async functions for the current turn.
338
+ * Updates the resolved request with turn-specific parameter values.
339
+ *
340
+ * @param turnContext - The turn context for parameter resolution
341
+ */
342
+ async resolveAsyncFunctionsForTurn(turnContext) {
343
+ if (hasAsyncFunctions(this.options.request)) {
344
+ const resolved = await resolveAsyncFunctions(this.options.request, turnContext);
345
+ this.resolvedRequest = { ...resolved, stream: false };
346
+ }
347
+ }
348
+ /**
349
+ * Apply nextTurnParams from executed tools.
350
+ * Allows tools to modify request parameters for subsequent turns.
351
+ *
352
+ * @param toolCalls - The tool calls that were just executed
353
+ */
354
+ async applyNextTurnParams(toolCalls) {
355
+ if (!this.options.tools || toolCalls.length === 0 || !this.resolvedRequest) {
356
+ return;
357
+ }
358
+ const computedParams = await executeNextTurnParamsFunctions(toolCalls, this.options.tools, this.resolvedRequest);
359
+ if (Object.keys(computedParams).length > 0) {
360
+ this.resolvedRequest = applyNextTurnParamsToRequest(this.resolvedRequest, computedParams);
361
+ }
362
+ }
363
+ /**
364
+ * Make a follow-up API request with tool results.
365
+ * Continues the conversation after tool execution.
366
+ *
367
+ * @param currentResponse - The response that contained tool calls
368
+ * @param toolResults - The results from executing those tools
369
+ * @returns The new response from the API
370
+ */
371
+ async makeFollowupRequest(currentResponse, toolResults) {
372
+ // Build new input with tool results
373
+ const newInput = [
374
+ ...(Array.isArray(currentResponse.output)
375
+ ? currentResponse.output
376
+ : [currentResponse.output]),
377
+ ...toolResults,
378
+ ];
379
+ if (!this.resolvedRequest) {
380
+ throw new Error('Request not initialized');
381
+ }
382
+ const newRequest = {
383
+ ...this.resolvedRequest,
384
+ input: newInput,
385
+ stream: false,
386
+ };
387
+ const newResult = await betaResponsesSend(this.options.client, newRequest, this.options.options);
388
+ if (!newResult.ok) {
389
+ throw newResult.error;
390
+ }
391
+ // Handle streaming or non-streaming response
392
+ const value = newResult.value;
393
+ if (isEventStream(value)) {
394
+ const stream = new ReusableReadableStream(value);
395
+ return consumeStreamForCompletion(stream);
396
+ }
397
+ else if (this.isNonStreamingResponse(value)) {
398
+ return value;
399
+ }
400
+ else {
401
+ throw new Error('Unexpected response type from API');
402
+ }
403
+ }
404
+ /**
405
+ * Validate the final response has required fields.
406
+ *
407
+ * @param response - The response to validate
408
+ * @throws Error if response is missing required fields or has invalid output
409
+ */
410
+ validateFinalResponse(response) {
411
+ if (!response?.id || !response?.output) {
412
+ throw new Error('Invalid final response: missing required fields');
413
+ }
414
+ if (!Array.isArray(response.output) || response.output.length === 0) {
415
+ throw new Error('Invalid final response: empty or invalid output');
416
+ }
417
+ }
418
+ /**
419
+ * Resolve async functions in the request for a given turn context.
420
+ * Extracts non-function fields and resolves any async parameter functions.
421
+ *
422
+ * @param context - The turn context for parameter resolution
423
+ * @returns The resolved request without async functions
424
+ */
425
+ async resolveRequestForContext(context) {
426
+ if (hasAsyncFunctions(this.options.request)) {
427
+ return resolveAsyncFunctions(this.options.request, context);
428
+ }
429
+ // Already resolved, extract non-function fields
430
+ // Filter out stopWhen and state-related fields that aren't part of the API request
431
+ const { stopWhen: _, state: _s, requireApproval: _r, approveToolCalls: _a, rejectToolCalls: _rj, ...rest } = this.options.request;
432
+ return rest;
433
+ }
434
+ /**
435
+ * Safely persist state with error handling.
436
+ * Wraps state save operations to ensure failures are properly reported.
437
+ *
438
+ * @param updates - Optional partial state updates to apply before saving
439
+ * @throws Error if state persistence fails
440
+ */
441
+ async saveStateSafely(updates) {
442
+ if (!this.stateAccessor || !this.currentState)
443
+ return;
444
+ if (updates) {
445
+ this.currentState = updateState(this.currentState, updates);
446
+ }
447
+ try {
448
+ await this.stateAccessor.save(this.currentState);
449
+ }
450
+ catch (error) {
451
+ const message = error instanceof Error ? error.message : String(error);
452
+ throw new Error(`Failed to persist conversation state: ${message}`);
453
+ }
454
+ }
455
+ /**
456
+ * Remove optional properties from state when they should be cleared.
457
+ * Uses delete to properly remove optional properties rather than setting undefined.
458
+ *
459
+ * @param props - Array of property names to remove from current state
460
+ */
461
+ clearOptionalStateProperties(props) {
462
+ if (!this.currentState)
463
+ return;
464
+ for (const prop of props) {
465
+ delete this.currentState[prop];
466
+ }
467
+ }
468
+ // =========================================================================
469
+ // Core Methods
470
+ // =========================================================================
82
471
  /**
83
472
  * Initialize the stream if not already started
84
473
  * This is idempotent - multiple calls will return the same promise
@@ -88,23 +477,57 @@ export class ModelResult {
88
477
  return this.initPromise;
89
478
  }
90
479
  this.initPromise = (async () => {
480
+ // Load or create state if accessor provided
481
+ if (this.stateAccessor) {
482
+ const loadedState = await this.stateAccessor.load();
483
+ if (loadedState) {
484
+ this.currentState = loadedState;
485
+ // Check if we're resuming from awaiting_approval with decisions
486
+ if (loadedState.status === 'awaiting_approval' &&
487
+ (this.approvedToolCalls.length > 0 || this.rejectedToolCalls.length > 0)) {
488
+ this.isResumingFromApproval = true;
489
+ await this.processApprovalDecisions();
490
+ return; // Skip normal initialization, we're resuming
491
+ }
492
+ // Check for interruption flag and handle
493
+ if (loadedState.interruptedBy) {
494
+ // Clear interruption flag and continue from saved state
495
+ this.currentState = updateState(loadedState, { status: 'in_progress' });
496
+ this.clearOptionalStateProperties(['interruptedBy']);
497
+ await this.saveStateSafely();
498
+ }
499
+ }
500
+ else {
501
+ this.currentState = createInitialState();
502
+ }
503
+ // Update status to in_progress
504
+ await this.saveStateSafely({ status: 'in_progress' });
505
+ }
91
506
  // Resolve async functions before initial request
92
507
  // Build initial turn context (turn 0 for initial request)
93
508
  const initialContext = {
94
509
  numberOfTurns: 0,
95
510
  };
96
511
  // Resolve any async functions first
97
- let baseRequest;
98
- if (hasAsyncFunctions(this.options.request)) {
99
- baseRequest = await resolveAsyncFunctions(this.options.request, initialContext);
100
- }
101
- else {
102
- // Already resolved, extract non-function fields
103
- // Since request is CallModelInput, we need to filter out stopWhen
104
- // Note: tools are already in API format at this point (converted in callModel())
105
- const { stopWhen: _, ...rest } = this.options.request;
106
- // Cast to ResolvedCallModelInput - we know it's resolved if hasAsyncFunctions returned false
107
- baseRequest = rest;
512
+ let baseRequest = await this.resolveRequestForContext(initialContext);
513
+ // If we have state with existing messages, use those as input
514
+ if (this.currentState && this.currentState.messages &&
515
+ Array.isArray(this.currentState.messages) && this.currentState.messages.length > 0) {
516
+ // Append new input to existing messages
517
+ const newInput = baseRequest.input;
518
+ if (newInput) {
519
+ const inputArray = Array.isArray(newInput) ? newInput : [newInput];
520
+ baseRequest = {
521
+ ...baseRequest,
522
+ input: appendToMessages(this.currentState.messages, inputArray),
523
+ };
524
+ }
525
+ else {
526
+ baseRequest = {
527
+ ...baseRequest,
528
+ input: this.currentState.messages,
529
+ };
530
+ }
108
531
  }
109
532
  // Store resolved request with stream mode
110
533
  this.resolvedRequest = {
@@ -113,22 +536,145 @@ export class ModelResult {
113
536
  };
114
537
  // Force stream mode for initial request
115
538
  const request = this.resolvedRequest;
116
- // Create the stream promise
117
- this.streamPromise = betaResponsesSend(this.options.client, request, this.options.options).then((result) => {
118
- if (!result.ok) {
119
- throw result.error;
120
- }
121
- // When stream: true, the API returns EventStream
122
- // TypeScript can't narrow the union type based on runtime parameter values,
123
- // so we assert the type here based on our knowledge that stream=true
124
- return result.value;
125
- });
126
- // Wait for the stream and create the reusable stream
127
- const eventStream = await this.streamPromise;
128
- this.reusableStream = new ReusableReadableStream(eventStream);
539
+ // Make the API request
540
+ const apiResult = await betaResponsesSend(this.options.client, request, this.options.options);
541
+ if (!apiResult.ok) {
542
+ throw apiResult.error;
543
+ }
544
+ // Handle both streaming and non-streaming responses
545
+ // The API may return a non-streaming response even when stream: true is requested
546
+ if (isEventStream(apiResult.value)) {
547
+ this.reusableStream = new ReusableReadableStream(apiResult.value);
548
+ }
549
+ else if (this.isNonStreamingResponse(apiResult.value)) {
550
+ // API returned a complete response directly - use it as the final response
551
+ this.finalResponse = apiResult.value;
552
+ }
553
+ else {
554
+ throw new Error('Unexpected response type from API');
555
+ }
129
556
  })();
130
557
  return this.initPromise;
131
558
  }
559
+ /**
560
+ * Process approval/rejection decisions and resume execution
561
+ */
562
+ async processApprovalDecisions() {
563
+ if (!this.currentState || !this.stateAccessor) {
564
+ throw new Error('Cannot process approval decisions without state');
565
+ }
566
+ const pendingCalls = this.currentState.pendingToolCalls ?? [];
567
+ const unsentResults = [...(this.currentState.unsentToolResults ?? [])];
568
+ // Build turn context - numberOfTurns represents the current turn (1-indexed after initial)
569
+ const turnContext = {
570
+ numberOfTurns: this.allToolExecutionRounds.length + 1,
571
+ };
572
+ // Process approvals - execute the approved tools
573
+ for (const callId of this.approvedToolCalls) {
574
+ const toolCall = pendingCalls.find(tc => tc.id === callId);
575
+ if (!toolCall)
576
+ continue;
577
+ const tool = this.options.tools?.find(t => t.function.name === toolCall.name);
578
+ if (!tool || !hasExecuteFunction(tool)) {
579
+ // Can't execute, create error result
580
+ unsentResults.push(createRejectedResult(callId, String(toolCall.name), 'Tool not found or not executable'));
581
+ continue;
582
+ }
583
+ const result = await executeTool(tool, toolCall, turnContext);
584
+ if (result.error) {
585
+ unsentResults.push(createRejectedResult(callId, String(toolCall.name), result.error.message));
586
+ }
587
+ else {
588
+ unsentResults.push(createUnsentResult(callId, String(toolCall.name), result.result));
589
+ }
590
+ }
591
+ // Process rejections
592
+ for (const callId of this.rejectedToolCalls) {
593
+ const toolCall = pendingCalls.find(tc => tc.id === callId);
594
+ if (!toolCall)
595
+ continue;
596
+ unsentResults.push(createRejectedResult(callId, String(toolCall.name), 'Rejected by user'));
597
+ }
598
+ // Remove processed calls from pending
599
+ const processedIds = new Set([...this.approvedToolCalls, ...this.rejectedToolCalls]);
600
+ const remainingPending = pendingCalls.filter(tc => !processedIds.has(tc.id));
601
+ // Update state - conditionally include optional properties only if they have values
602
+ const stateUpdates = {
603
+ status: remainingPending.length > 0 ? 'awaiting_approval' : 'in_progress',
604
+ };
605
+ if (remainingPending.length > 0) {
606
+ stateUpdates.pendingToolCalls = remainingPending;
607
+ }
608
+ if (unsentResults.length > 0) {
609
+ stateUpdates.unsentToolResults = unsentResults;
610
+ }
611
+ await this.saveStateSafely(stateUpdates);
612
+ // Clear optional properties if they should be empty
613
+ const propsToClear = [];
614
+ if (remainingPending.length === 0)
615
+ propsToClear.push('pendingToolCalls');
616
+ if (unsentResults.length === 0)
617
+ propsToClear.push('unsentToolResults');
618
+ if (propsToClear.length > 0) {
619
+ this.clearOptionalStateProperties(propsToClear);
620
+ await this.saveStateSafely();
621
+ }
622
+ // If we still have pending approvals, stop here
623
+ if (remainingPending.length > 0) {
624
+ return;
625
+ }
626
+ // Otherwise, continue with tool execution using unsent results
627
+ await this.continueWithUnsentResults();
628
+ }
629
+ /**
630
+ * Continue execution with unsent tool results
631
+ */
632
+ async continueWithUnsentResults() {
633
+ if (!this.currentState || !this.stateAccessor)
634
+ return;
635
+ const unsentResults = this.currentState.unsentToolResults ?? [];
636
+ if (unsentResults.length === 0)
637
+ return;
638
+ // Convert to API format
639
+ const toolOutputs = unsentResultsToAPIFormat(unsentResults);
640
+ // Build new input with tool results
641
+ const currentMessages = this.currentState.messages;
642
+ const newInput = appendToMessages(currentMessages, toolOutputs);
643
+ // Clear unsent results from state
644
+ this.currentState = updateState(this.currentState, {
645
+ messages: newInput,
646
+ });
647
+ this.clearOptionalStateProperties(['unsentToolResults']);
648
+ await this.saveStateSafely();
649
+ // Build request with the updated input
650
+ // numberOfTurns represents the current turn number (1-indexed after initial)
651
+ const turnContext = {
652
+ numberOfTurns: this.allToolExecutionRounds.length + 1,
653
+ };
654
+ const baseRequest = await this.resolveRequestForContext(turnContext);
655
+ // Create request with the accumulated messages
656
+ const request = {
657
+ ...baseRequest,
658
+ input: newInput,
659
+ stream: true,
660
+ };
661
+ this.resolvedRequest = request;
662
+ // Make the API request
663
+ const apiResult = await betaResponsesSend(this.options.client, request, this.options.options);
664
+ if (!apiResult.ok) {
665
+ throw apiResult.error;
666
+ }
667
+ // Handle both streaming and non-streaming responses
668
+ if (isEventStream(apiResult.value)) {
669
+ this.reusableStream = new ReusableReadableStream(apiResult.value);
670
+ }
671
+ else if (this.isNonStreamingResponse(apiResult.value)) {
672
+ this.finalResponse = apiResult.value;
673
+ }
674
+ else {
675
+ throw new Error('Unexpected response type from API');
676
+ }
677
+ }
132
678
  /**
133
679
  * Execute tools automatically if they are provided and have execute functions
134
680
  * This is idempotent - multiple calls will return the same promise
@@ -139,179 +685,81 @@ export class ModelResult {
139
685
  }
140
686
  this.toolExecutionPromise = (async () => {
141
687
  await this.initStream();
142
- if (!this.reusableStream) {
143
- throw new Error('Stream not initialized');
688
+ // If resuming from approval and still pending, don't continue
689
+ if (this.isResumingFromApproval && this.currentState?.status === 'awaiting_approval') {
690
+ return;
144
691
  }
145
- // Note: Async functions already resolved in initStream()
146
- // Get the initial response
147
- const initialResponse = await consumeStreamForCompletion(this.reusableStream);
148
- // Check if we have tools and if auto-execution is enabled
149
- const shouldAutoExecute = this.options.tools &&
150
- this.options.tools.length > 0 &&
151
- initialResponse.output.some((item) => hasTypeProperty(item) && item.type === 'function_call');
152
- if (!shouldAutoExecute) {
153
- // No tools to execute, use initial response
154
- this.finalResponse = initialResponse;
692
+ // Get initial response
693
+ let currentResponse = await this.getInitialResponse();
694
+ // Save initial response to state
695
+ await this.saveResponseToState(currentResponse);
696
+ // Check if tools should be executed
697
+ const hasToolCalls = currentResponse.output.some((item) => hasTypeProperty(item) && item.type === 'function_call');
698
+ if (!this.options.tools?.length || !hasToolCalls) {
699
+ this.finalResponse = currentResponse;
700
+ await this.markStateComplete();
155
701
  return;
156
702
  }
157
- // Extract tool calls
158
- const toolCalls = extractToolCallsFromResponse(initialResponse);
159
- // Check if any have execute functions
160
- const executableTools = toolCalls.filter((toolCall) => {
161
- const tool = this.options.tools?.find((t) => t.function.name === toolCall.name);
162
- return tool && hasExecuteFunction(tool);
163
- });
164
- if (executableTools.length === 0) {
165
- // No executable tools, use initial response
166
- this.finalResponse = initialResponse;
703
+ // Extract and check tool calls
704
+ const toolCalls = extractToolCallsFromResponse(currentResponse);
705
+ // Check for approval requirements
706
+ if (await this.handleApprovalCheck(toolCalls, 0, currentResponse)) {
707
+ return; // Paused for approval
708
+ }
709
+ if (!this.hasExecutableToolCalls(toolCalls)) {
710
+ this.finalResponse = currentResponse;
711
+ await this.markStateComplete();
167
712
  return;
168
713
  }
169
- let currentResponse = initialResponse;
714
+ // Main execution loop
170
715
  let currentRound = 0;
171
716
  while (true) {
172
- // Check stopWhen conditions
173
- if (this.options.stopWhen) {
174
- const stopConditions = Array.isArray(this.options.stopWhen)
175
- ? this.options.stopWhen
176
- : [this.options.stopWhen];
177
- const shouldStop = await isStopConditionMet({
178
- stopConditions,
179
- steps: this.allToolExecutionRounds.map((round) => ({
180
- stepType: 'continue',
181
- text: extractTextFromResponse(round.response),
182
- toolCalls: round.toolCalls,
183
- toolResults: round.toolResults.map((tr) => ({
184
- toolCallId: tr.callId,
185
- toolName: round.toolCalls.find((tc) => tc.id === tr.callId)?.name ?? '',
186
- result: JSON.parse(tr.output),
187
- })),
188
- response: round.response,
189
- usage: round.response.usage,
190
- finishReason: undefined, // OpenResponsesNonStreamingResponse doesn't have finishReason
191
- })),
192
- });
193
- if (shouldStop) {
194
- break;
195
- }
717
+ // Check for external interruption
718
+ if (await this.checkForInterruption(currentResponse)) {
719
+ return;
720
+ }
721
+ // Check stop conditions
722
+ if (await this.shouldStopExecution()) {
723
+ break;
196
724
  }
197
725
  const currentToolCalls = extractToolCallsFromResponse(currentResponse);
198
726
  if (currentToolCalls.length === 0) {
199
727
  break;
200
728
  }
201
- const hasExecutable = currentToolCalls.some((toolCall) => {
202
- const tool = this.options.tools?.find((t) => t.function.name === toolCall.name);
203
- return tool && hasExecuteFunction(tool);
204
- });
205
- if (!hasExecutable) {
729
+ // Check for approval requirements
730
+ if (await this.handleApprovalCheck(currentToolCalls, currentRound + 1, currentResponse)) {
731
+ return;
732
+ }
733
+ if (!this.hasExecutableToolCalls(currentToolCalls)) {
206
734
  break;
207
735
  }
208
- // Build turn context for this round (for async parameter resolution only)
209
- const turnContext = {
210
- numberOfTurns: currentRound + 1, // 1-indexed
211
- };
736
+ // Build turn context
737
+ const turnContext = { numberOfTurns: currentRound + 1 };
212
738
  // Resolve async functions for this turn
213
- if (hasAsyncFunctions(this.options.request)) {
214
- const resolved = await resolveAsyncFunctions(this.options.request, turnContext);
215
- // Update resolved request with new values
216
- this.resolvedRequest = {
217
- ...resolved,
218
- stream: false, // Tool execution turns don't need streaming
219
- };
220
- }
221
- // Execute all tool calls
222
- const toolResults = [];
223
- for (const toolCall of currentToolCalls) {
224
- const tool = this.options.tools?.find((t) => t.function.name === toolCall.name);
225
- if (!tool || !hasExecuteFunction(tool)) {
226
- continue;
227
- }
228
- // Create callback for real-time preliminary results
229
- const onPreliminaryResult = this.toolEventBroadcaster
230
- ? (callId, resultValue) => {
231
- this.toolEventBroadcaster?.push({
232
- type: 'preliminary_result',
233
- toolCallId: callId,
234
- result: resultValue,
235
- });
236
- }
237
- : undefined;
238
- const result = await executeTool(tool, toolCall, turnContext, onPreliminaryResult);
239
- toolResults.push({
240
- type: 'function_call_output',
241
- id: `output_${toolCall.id}`,
242
- callId: toolCall.id,
243
- output: result.error
244
- ? JSON.stringify({
245
- error: result.error.message,
246
- })
247
- : JSON.stringify(result.result),
248
- });
249
- }
250
- // Store execution round info including tool results
739
+ await this.resolveAsyncFunctionsForTurn(turnContext);
740
+ // Execute tools
741
+ const toolResults = await this.executeToolRound(currentToolCalls, turnContext);
742
+ // Track execution round
251
743
  this.allToolExecutionRounds.push({
252
744
  round: currentRound,
253
745
  toolCalls: currentToolCalls,
254
746
  response: currentResponse,
255
747
  toolResults,
256
748
  });
257
- // Execute nextTurnParams functions for tools that were called
258
- if (this.options.tools && currentToolCalls.length > 0) {
259
- if (!this.resolvedRequest) {
260
- throw new Error('Request not initialized');
261
- }
262
- const computedParams = await executeNextTurnParamsFunctions(currentToolCalls, this.options.tools, this.resolvedRequest);
263
- // Apply computed parameters to the resolved request for next turn
264
- if (Object.keys(computedParams).length > 0) {
265
- this.resolvedRequest = applyNextTurnParamsToRequest(this.resolvedRequest, computedParams);
266
- }
267
- }
268
- // Build new input with tool results
269
- // For the Responses API, we need to include the tool results in the input
270
- const newInput = [
271
- ...(Array.isArray(currentResponse.output)
272
- ? currentResponse.output
273
- : [
274
- currentResponse.output,
275
- ]),
276
- ...toolResults,
277
- ];
278
- // Make new request with tool results
279
- if (!this.resolvedRequest) {
280
- throw new Error('Request not initialized');
281
- }
282
- const newRequest = {
283
- ...this.resolvedRequest,
284
- input: newInput,
285
- stream: false,
286
- };
287
- const newResult = await betaResponsesSend(this.options.client, newRequest, this.options.options);
288
- if (!newResult.ok) {
289
- throw newResult.error;
290
- }
291
- // Handle the result - it might be a stream or a response
292
- const value = newResult.value;
293
- if (isEventStream(value)) {
294
- // It's a stream, consume it
295
- const stream = new ReusableReadableStream(value);
296
- currentResponse = await consumeStreamForCompletion(stream);
297
- }
298
- else if (this.isNonStreamingResponse(value)) {
299
- currentResponse = value;
300
- }
301
- else {
302
- throw new Error('Unexpected response type from API');
303
- }
749
+ // Save tool results to state
750
+ await this.saveToolResultsToState(toolResults);
751
+ // Apply nextTurnParams
752
+ await this.applyNextTurnParams(currentToolCalls);
753
+ // Make follow-up request
754
+ currentResponse = await this.makeFollowupRequest(currentResponse, toolResults);
755
+ // Save new response to state
756
+ await this.saveResponseToState(currentResponse);
304
757
  currentRound++;
305
758
  }
306
- // Validate the final response has required fields
307
- if (!currentResponse || !currentResponse.id || !currentResponse.output) {
308
- throw new Error('Invalid final response: missing required fields');
309
- }
310
- // Ensure the response is in a completed state (has output content)
311
- if (!Array.isArray(currentResponse.output) || currentResponse.output.length === 0) {
312
- throw new Error('Invalid final response: empty or invalid output');
313
- }
759
+ // Validate and finalize
760
+ this.validateFinalResponse(currentResponse);
314
761
  this.finalResponse = currentResponse;
762
+ await this.markStateComplete();
315
763
  })();
316
764
  return this.toolExecutionPromise;
317
765
  }
@@ -484,6 +932,10 @@ export class ModelResult {
484
932
  */
485
933
  async getToolCalls() {
486
934
  await this.initStream();
935
+ // Handle non-streaming response case - use finalResponse directly
936
+ if (this.finalResponse) {
937
+ return extractToolCallsFromResponse(this.finalResponse);
938
+ }
487
939
  if (!this.reusableStream) {
488
940
  throw new Error('Stream not initialized');
489
941
  }
@@ -511,5 +963,50 @@ export class ModelResult {
511
963
  await this.reusableStream.cancel();
512
964
  }
513
965
  }
966
+ // =========================================================================
967
+ // Multi-Turn Conversation State Methods
968
+ // =========================================================================
969
+ /**
970
+ * Check if the conversation requires human approval to continue.
971
+ * Returns true if there are pending tool calls awaiting approval.
972
+ */
973
+ async requiresApproval() {
974
+ await this.initStream();
975
+ // If we have pending tool calls in state, approval is required
976
+ if (this.currentState?.status === 'awaiting_approval') {
977
+ return true;
978
+ }
979
+ // Also check if pendingToolCalls is populated
980
+ return (this.currentState?.pendingToolCalls?.length ?? 0) > 0;
981
+ }
982
+ /**
983
+ * Get the pending tool calls that require approval.
984
+ * Returns empty array if no approvals needed.
985
+ */
986
+ async getPendingToolCalls() {
987
+ await this.initStream();
988
+ // Try to trigger tool execution to populate pending calls
989
+ if (!this.isResumingFromApproval) {
990
+ await this.executeToolsIfNeeded();
991
+ }
992
+ return (this.currentState?.pendingToolCalls ?? []);
993
+ }
994
+ /**
995
+ * Get the current conversation state.
996
+ * Useful for inspection, debugging, or custom persistence.
997
+ * Note: This returns the raw ConversationState for inspection only.
998
+ * To resume a conversation, use the StateAccessor pattern.
999
+ */
1000
+ async getState() {
1001
+ await this.initStream();
1002
+ // Ensure tool execution has been attempted (to populate final state)
1003
+ if (!this.isResumingFromApproval) {
1004
+ await this.executeToolsIfNeeded();
1005
+ }
1006
+ if (!this.currentState) {
1007
+ throw new Error('State not initialized. Make sure a StateAccessor was provided to callModel.');
1008
+ }
1009
+ return this.currentState;
1010
+ }
514
1011
  }
515
1012
  //# sourceMappingURL=model-result.js.map