@mariozechner/pi-ai 0.5.39 → 0.5.41
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/README.md +86 -10
- package/dist/json-parse.d.ts +9 -0
- package/dist/json-parse.d.ts.map +1 -0
- package/dist/json-parse.js +29 -0
- package/dist/json-parse.js.map +1 -0
- package/dist/models.generated.d.ts +55 -4
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +119 -68
- package/dist/models.generated.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +2 -0
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +2 -0
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +2 -7
- package/dist/providers/openai-responses.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -71,8 +71,19 @@ for await (const event of s) {
|
|
|
71
71
|
case 'thinking_end':
|
|
72
72
|
console.log('[Thinking complete]');
|
|
73
73
|
break;
|
|
74
|
-
case '
|
|
74
|
+
case 'toolcall_start':
|
|
75
|
+
console.log(`\n[Tool call started: index ${event.contentIndex}]`);
|
|
76
|
+
break;
|
|
77
|
+
case 'toolcall_delta':
|
|
78
|
+
// Partial tool arguments are being streamed
|
|
79
|
+
const partialCall = event.partial.content[event.contentIndex];
|
|
80
|
+
if (partialCall.type === 'toolCall') {
|
|
81
|
+
console.log(`[Streaming args for ${partialCall.name}]`);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case 'toolcall_end':
|
|
75
85
|
console.log(`\nTool called: ${event.toolCall.name}`);
|
|
86
|
+
console.log(`Arguments: ${JSON.stringify(event.toolCall.arguments)}`);
|
|
76
87
|
break;
|
|
77
88
|
case 'done':
|
|
78
89
|
console.log(`\nFinished: ${event.reason}`);
|
|
@@ -84,9 +95,10 @@ for await (const event of s) {
|
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
// Get the final message after streaming, add it to the context
|
|
87
|
-
const finalMessage = await s.
|
|
98
|
+
const finalMessage = await s.result();
|
|
88
99
|
context.messages.push(finalMessage);
|
|
89
100
|
|
|
101
|
+
// Handle tool calls if any
|
|
90
102
|
// Handle tool calls if any
|
|
91
103
|
const toolCalls = finalMessage.content.filter(b => b.type === 'toolCall');
|
|
92
104
|
for (const call of toolCalls) {
|
|
@@ -194,6 +206,70 @@ for (const block of response.content) {
|
|
|
194
206
|
}
|
|
195
207
|
```
|
|
196
208
|
|
|
209
|
+
### Streaming Tool Calls with Partial JSON
|
|
210
|
+
|
|
211
|
+
During streaming, tool call arguments are progressively parsed as they arrive. This enables real-time UI updates before the complete arguments are available:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const s = stream(model, context);
|
|
215
|
+
|
|
216
|
+
for await (const event of s) {
|
|
217
|
+
if (event.type === 'toolcall_delta') {
|
|
218
|
+
const toolCall = event.partial.content[event.contentIndex];
|
|
219
|
+
|
|
220
|
+
// toolCall.arguments contains partially parsed JSON during streaming
|
|
221
|
+
// This allows for progressive UI updates
|
|
222
|
+
if (toolCall.type === 'toolCall' && toolCall.arguments) {
|
|
223
|
+
// BE DEFENSIVE: arguments may be incomplete
|
|
224
|
+
// Example: Show file path being written even before content is complete
|
|
225
|
+
if (toolCall.name === 'write_file' && toolCall.arguments.path) {
|
|
226
|
+
console.log(`Writing to: ${toolCall.arguments.path}`);
|
|
227
|
+
|
|
228
|
+
// Content might be partial or missing
|
|
229
|
+
if (toolCall.arguments.content) {
|
|
230
|
+
console.log(`Content preview: ${toolCall.arguments.content.substring(0, 100)}...`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (event.type === 'toolcall_end') {
|
|
237
|
+
// Here toolCall.arguments is complete and validated
|
|
238
|
+
const toolCall = event.toolCall;
|
|
239
|
+
console.log(`Tool completed: ${toolCall.name}`, toolCall.arguments);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Important notes about partial tool arguments:**
|
|
245
|
+
- During `toolcall_delta` events, `arguments` contains the best-effort parse of partial JSON
|
|
246
|
+
- Fields may be missing or incomplete - always check for existence before use
|
|
247
|
+
- String values may be truncated mid-word
|
|
248
|
+
- Arrays may be incomplete
|
|
249
|
+
- Nested objects may be partially populated
|
|
250
|
+
- At minimum, `arguments` will be an empty object `{}`, never `undefined`
|
|
251
|
+
- Full validation only occurs at `toolcall_end` when arguments are complete
|
|
252
|
+
- The Google provider does not support function call streaming. Instead, you will receive a single `toolcall_delta` event with the full arguments.
|
|
253
|
+
|
|
254
|
+
### Complete Event Reference
|
|
255
|
+
|
|
256
|
+
All streaming events emitted during assistant message generation:
|
|
257
|
+
|
|
258
|
+
| Event Type | Description | Key Properties |
|
|
259
|
+
|------------|-------------|----------------|
|
|
260
|
+
| `start` | Stream begins | `partial`: Initial assistant message structure |
|
|
261
|
+
| `text_start` | Text block starts | `contentIndex`: Position in content array |
|
|
262
|
+
| `text_delta` | Text chunk received | `delta`: New text, `contentIndex`: Position |
|
|
263
|
+
| `text_end` | Text block complete | `content`: Full text, `contentIndex`: Position |
|
|
264
|
+
| `thinking_start` | Thinking block starts | `contentIndex`: Position in content array |
|
|
265
|
+
| `thinking_delta` | Thinking chunk received | `delta`: New text, `contentIndex`: Position |
|
|
266
|
+
| `thinking_end` | Thinking block complete | `content`: Full thinking, `contentIndex`: Position |
|
|
267
|
+
| `toolcall_start` | Tool call begins | `contentIndex`: Position in content array |
|
|
268
|
+
| `toolcall_delta` | Tool arguments streaming | `delta`: JSON chunk, `partial.content[contentIndex].arguments`: Partial parsed args |
|
|
269
|
+
| `toolcall_end` | Tool call complete | `toolCall`: Complete validated tool call with `id`, `name`, `arguments` |
|
|
270
|
+
| `done` | Stream complete | `reason`: Stop reason, `message`: Final assistant message |
|
|
271
|
+
| `error` | Error occurred | `error`: Error message, `partial`: Partial message before error |
|
|
272
|
+
|
|
197
273
|
## Image Input
|
|
198
274
|
|
|
199
275
|
Models with vision capabilities can process images. You can check if a model supports images via the `input` property. If you pass images to a non-vision model, they are silently ignored.
|
|
@@ -358,7 +434,7 @@ for await (const event of s) {
|
|
|
358
434
|
}
|
|
359
435
|
|
|
360
436
|
// Get results (may be partial if aborted)
|
|
361
|
-
const response = await s.
|
|
437
|
+
const response = await s.result();
|
|
362
438
|
if (response.stopReason === 'error') {
|
|
363
439
|
console.log('Error:', response.error);
|
|
364
440
|
console.log('Partial content received:', response.content);
|
|
@@ -642,26 +718,26 @@ for await (const event of stream) {
|
|
|
642
718
|
case 'agent_start':
|
|
643
719
|
console.log('Agent started');
|
|
644
720
|
break;
|
|
645
|
-
|
|
721
|
+
|
|
646
722
|
case 'turn_start':
|
|
647
723
|
console.log('New turn started');
|
|
648
724
|
break;
|
|
649
|
-
|
|
725
|
+
|
|
650
726
|
case 'message_start':
|
|
651
727
|
console.log(`${event.message.role} message started`);
|
|
652
728
|
break;
|
|
653
|
-
|
|
729
|
+
|
|
654
730
|
case 'message_update':
|
|
655
731
|
// Only for assistant messages during streaming
|
|
656
732
|
if (event.message.content.some(c => c.type === 'text')) {
|
|
657
733
|
console.log('Assistant:', event.message.content);
|
|
658
734
|
}
|
|
659
735
|
break;
|
|
660
|
-
|
|
736
|
+
|
|
661
737
|
case 'tool_execution_start':
|
|
662
738
|
console.log(`Calling ${event.toolName} with:`, event.args);
|
|
663
739
|
break;
|
|
664
|
-
|
|
740
|
+
|
|
665
741
|
case 'tool_execution_end':
|
|
666
742
|
if (event.isError) {
|
|
667
743
|
console.error(`Tool failed:`, event.result);
|
|
@@ -669,11 +745,11 @@ for await (const event of stream) {
|
|
|
669
745
|
console.log(`Tool result:`, event.result.output);
|
|
670
746
|
}
|
|
671
747
|
break;
|
|
672
|
-
|
|
748
|
+
|
|
673
749
|
case 'turn_end':
|
|
674
750
|
console.log(`Turn ended with ${event.toolResults.length} tool calls`);
|
|
675
751
|
break;
|
|
676
|
-
|
|
752
|
+
|
|
677
753
|
case 'agent_end':
|
|
678
754
|
console.log(`Agent completed with ${event.messages.length} new messages`);
|
|
679
755
|
break;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attempts to parse potentially incomplete JSON during streaming.
|
|
3
|
+
* Always returns a valid object, even if the JSON is incomplete.
|
|
4
|
+
*
|
|
5
|
+
* @param partialJson The partial JSON string from streaming
|
|
6
|
+
* @returns Parsed object or empty object if parsing fails
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseStreamingJson<T = any>(partialJson: string | undefined): T;
|
|
9
|
+
//# sourceMappingURL=json-parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-parse.d.ts","sourceRoot":"","sources":["../src/json-parse.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,GAAG,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,CAAC,CAkB9E"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { parse as partialParse } from "partial-json";
|
|
2
|
+
/**
|
|
3
|
+
* Attempts to parse potentially incomplete JSON during streaming.
|
|
4
|
+
* Always returns a valid object, even if the JSON is incomplete.
|
|
5
|
+
*
|
|
6
|
+
* @param partialJson The partial JSON string from streaming
|
|
7
|
+
* @returns Parsed object or empty object if parsing fails
|
|
8
|
+
*/
|
|
9
|
+
export function parseStreamingJson(partialJson) {
|
|
10
|
+
if (!partialJson || partialJson.trim() === "") {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
// Try standard parsing first (fastest for complete JSON)
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(partialJson);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Try partial-json for incomplete JSON
|
|
19
|
+
try {
|
|
20
|
+
const result = partialParse(partialJson);
|
|
21
|
+
return (result ?? {});
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// If all parsing fails, return empty object
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=json-parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-parse.js","sourceRoot":"","sources":["../src/json-parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,YAAY,EAAE,MAAM,cAAc,CAAC;AAErD;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAU,WAA+B;IAC1E,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/C,OAAO,EAAO,CAAC;IAChB,CAAC;IAED,yDAAyD;IACzD,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAM,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACR,uCAAuC;QACvC,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;YACzC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAM,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACR,4CAA4C;YAC5C,OAAO,EAAO,CAAC;QAChB,CAAC;IACF,CAAC;AACF,CAAC","sourcesContent":["import { parse as partialParse } from \"partial-json\";\n\n/**\n * Attempts to parse potentially incomplete JSON during streaming.\n * Always returns a valid object, even if the JSON is incomplete.\n *\n * @param partialJson The partial JSON string from streaming\n * @returns Parsed object or empty object if parsing fails\n */\nexport function parseStreamingJson<T = any>(partialJson: string | undefined): T {\n\tif (!partialJson || partialJson.trim() === \"\") {\n\t\treturn {} as T;\n\t}\n\n\t// Try standard parsing first (fastest for complete JSON)\n\ttry {\n\t\treturn JSON.parse(partialJson) as T;\n\t} catch {\n\t\t// Try partial-json for incomplete JSON\n\t\ttry {\n\t\t\tconst result = partialParse(partialJson);\n\t\t\treturn (result ?? {}) as T;\n\t\t} catch {\n\t\t\t// If all parsing fails, return empty object\n\t\t\treturn {} as T;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1408,6 +1408,40 @@ export declare const MODELS: {
|
|
|
1408
1408
|
};
|
|
1409
1409
|
};
|
|
1410
1410
|
readonly openrouter: {
|
|
1411
|
+
readonly "qwen/qwen3-coder-flash": {
|
|
1412
|
+
id: string;
|
|
1413
|
+
name: string;
|
|
1414
|
+
api: "openai-completions";
|
|
1415
|
+
provider: string;
|
|
1416
|
+
baseUrl: string;
|
|
1417
|
+
reasoning: false;
|
|
1418
|
+
input: "text"[];
|
|
1419
|
+
cost: {
|
|
1420
|
+
input: number;
|
|
1421
|
+
output: number;
|
|
1422
|
+
cacheRead: number;
|
|
1423
|
+
cacheWrite: number;
|
|
1424
|
+
};
|
|
1425
|
+
contextWindow: number;
|
|
1426
|
+
maxTokens: number;
|
|
1427
|
+
};
|
|
1428
|
+
readonly "qwen/qwen3-coder-plus": {
|
|
1429
|
+
id: string;
|
|
1430
|
+
name: string;
|
|
1431
|
+
api: "openai-completions";
|
|
1432
|
+
provider: string;
|
|
1433
|
+
baseUrl: string;
|
|
1434
|
+
reasoning: false;
|
|
1435
|
+
input: "text"[];
|
|
1436
|
+
cost: {
|
|
1437
|
+
input: number;
|
|
1438
|
+
output: number;
|
|
1439
|
+
cacheRead: number;
|
|
1440
|
+
cacheWrite: number;
|
|
1441
|
+
};
|
|
1442
|
+
contextWindow: number;
|
|
1443
|
+
maxTokens: number;
|
|
1444
|
+
};
|
|
1411
1445
|
readonly "qwen/qwen3-next-80b-a3b-thinking": {
|
|
1412
1446
|
id: string;
|
|
1413
1447
|
name: string;
|
|
@@ -2513,6 +2547,23 @@ export declare const MODELS: {
|
|
|
2513
2547
|
contextWindow: number;
|
|
2514
2548
|
maxTokens: number;
|
|
2515
2549
|
};
|
|
2550
|
+
readonly "microsoft/phi-4-multimodal-instruct": {
|
|
2551
|
+
id: string;
|
|
2552
|
+
name: string;
|
|
2553
|
+
api: "openai-completions";
|
|
2554
|
+
provider: string;
|
|
2555
|
+
baseUrl: string;
|
|
2556
|
+
reasoning: false;
|
|
2557
|
+
input: ("text" | "image")[];
|
|
2558
|
+
cost: {
|
|
2559
|
+
input: number;
|
|
2560
|
+
output: number;
|
|
2561
|
+
cacheRead: number;
|
|
2562
|
+
cacheWrite: number;
|
|
2563
|
+
};
|
|
2564
|
+
contextWindow: number;
|
|
2565
|
+
maxTokens: number;
|
|
2566
|
+
};
|
|
2516
2567
|
readonly "qwen/qwq-32b": {
|
|
2517
2568
|
id: string;
|
|
2518
2569
|
name: string;
|
|
@@ -2938,7 +2989,7 @@ export declare const MODELS: {
|
|
|
2938
2989
|
contextWindow: number;
|
|
2939
2990
|
maxTokens: number;
|
|
2940
2991
|
};
|
|
2941
|
-
readonly "cohere/command-r-
|
|
2992
|
+
readonly "cohere/command-r-08-2024": {
|
|
2942
2993
|
id: string;
|
|
2943
2994
|
name: string;
|
|
2944
2995
|
api: "openai-completions";
|
|
@@ -2955,7 +3006,7 @@ export declare const MODELS: {
|
|
|
2955
3006
|
contextWindow: number;
|
|
2956
3007
|
maxTokens: number;
|
|
2957
3008
|
};
|
|
2958
|
-
readonly "cohere/command-r-08-2024": {
|
|
3009
|
+
readonly "cohere/command-r-plus-08-2024": {
|
|
2959
3010
|
id: string;
|
|
2960
3011
|
name: string;
|
|
2961
3012
|
api: "openai-completions";
|
|
@@ -3159,7 +3210,7 @@ export declare const MODELS: {
|
|
|
3159
3210
|
contextWindow: number;
|
|
3160
3211
|
maxTokens: number;
|
|
3161
3212
|
};
|
|
3162
|
-
readonly "meta-llama/llama-3-
|
|
3213
|
+
readonly "meta-llama/llama-3-8b-instruct": {
|
|
3163
3214
|
id: string;
|
|
3164
3215
|
name: string;
|
|
3165
3216
|
api: "openai-completions";
|
|
@@ -3176,7 +3227,7 @@ export declare const MODELS: {
|
|
|
3176
3227
|
contextWindow: number;
|
|
3177
3228
|
maxTokens: number;
|
|
3178
3229
|
};
|
|
3179
|
-
readonly "meta-llama/llama-3-
|
|
3230
|
+
readonly "meta-llama/llama-3-70b-instruct": {
|
|
3180
3231
|
id: string;
|
|
3181
3232
|
name: string;
|
|
3182
3233
|
api: "openai-completions";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.generated.d.ts","sourceRoot":"","sources":["../src/models.generated.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"models.generated.d.ts","sourceRoot":"","sources":["../src/models.generated.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAy1GT,CAAC"}
|