diff --git a/.gitignore b/.gitignore index 211d06aa199..8fc0387dcda 100644 --- a/.gitignore +++ b/.gitignore @@ -23,8 +23,11 @@ docs/_site/ # Dotenv .env.integration -#Local lint config +# Local lint config .eslintrc.local.json -#Logging +# Logging logs + +# LanceDB +lancedb diff --git a/.vscodeignore b/.vscodeignore index 19d2ddfe364..6c6f53f66f8 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -5,6 +5,7 @@ .vscode-test/** out/** node_modules/** +integration-tests/** src/** .gitignore .yarnrc @@ -41,4 +42,7 @@ webview-ui/node_modules/** !src/integrations/theme/default-themes/** # Include icons -!assets/icons/** \ No newline at end of file +!assets/icons/** + +# LanceDB +lancedb/** diff --git a/src/test/VSCODE_INTEGRATION_TESTS.md b/integration-tests/VSCODE_INTEGRATION_TESTS.md similarity index 100% rename from src/test/VSCODE_INTEGRATION_TESTS.md rename to integration-tests/VSCODE_INTEGRATION_TESTS.md diff --git a/src/test/runTest.ts b/integration-tests/runTest.ts similarity index 100% rename from src/test/runTest.ts rename to integration-tests/runTest.ts diff --git a/src/test/suite/extension.test.ts b/integration-tests/suite/extension.test.ts similarity index 96% rename from src/test/suite/extension.test.ts rename to integration-tests/suite/extension.test.ts index 969087ff02d..35fd099d935 100644 --- a/src/test/suite/extension.test.ts +++ b/integration-tests/suite/extension.test.ts @@ -27,7 +27,7 @@ suite("Roo Code Extension", () => { while (Date.now() - startTime < timeout) { const commands = await vscode.commands.getCommands(true) - const missingCommands = [] + const missingCommands: string[] = [] for (const cmd of expectedCommands) { if (!commands.includes(cmd)) { diff --git a/src/test/suite/index.ts b/integration-tests/suite/index.ts similarity index 89% rename from src/test/suite/index.ts rename to integration-tests/suite/index.ts index ffb8de7473e..df790f36a63 100644 --- a/src/test/suite/index.ts +++ b/integration-tests/suite/index.ts @@ -1,17 +1,8 @@ import * as path from "path" import Mocha from "mocha" import { glob } from "glob" -import { ClineAPI } from "../../exports/cline" -import { ClineProvider } from "../../core/webview/ClineProvider" import * as vscode from "vscode" -declare global { - var api: ClineAPI - var provider: ClineProvider - var extension: vscode.Extension | undefined - var panel: vscode.WebviewPanel | undefined -} - export async function run(): Promise { // Create the mocha test const mocha = new Mocha({ diff --git a/src/test/suite/modes.test.ts b/integration-tests/suite/modes.test.ts similarity index 98% rename from src/test/suite/modes.test.ts rename to integration-tests/suite/modes.test.ts index 2fe0eaa597f..89792365c66 100644 --- a/src/test/suite/modes.test.ts +++ b/integration-tests/suite/modes.test.ts @@ -1,5 +1,4 @@ import * as assert from "assert" -import * as vscode from "vscode" suite("Roo Code Modes", () => { test("Should handle switching modes correctly", async function () { diff --git a/src/test/suite/task.test.ts b/integration-tests/suite/task.test.ts similarity index 97% rename from src/test/suite/task.test.ts rename to integration-tests/suite/task.test.ts index 2d34bc78ff3..cb73bfa334c 100644 --- a/src/test/suite/task.test.ts +++ b/integration-tests/suite/task.test.ts @@ -1,5 +1,4 @@ import * as assert from "assert" -import * as vscode from "vscode" suite("Roo Code Task", () => { test("Should handle prompt and response correctly", async function () { diff --git a/package-lock.json b/package-lock.json index 36619b8d43c..3bea7c2340c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@anthropic-ai/vertex-sdk": "^0.4.1", "@aws-sdk/client-bedrock-runtime": "^3.706.0", "@google/generative-ai": "^0.18.0", + "@lancedb/lancedb": "^0.16.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.0.1", "@types/clone-deep": "^4.0.4", @@ -49,9 +50,11 @@ "string-similarity": "^4.0.4", "strip-ansi": "^7.1.0", "tmp": "^0.2.3", - "tree-sitter-wasms": "^0.1.11", + "tree-sitter-wasms": "^0.1.12", "turndown": "^7.2.0", - "web-tree-sitter": "^0.22.6", + "uri-js": "^4.4.1", + "uuid": "^11.0.5", + "web-tree-sitter": "^0.25.1", "zod": "^3.23.8" }, "devDependencies": { @@ -79,6 +82,7 @@ "lint-staged": "^15.2.11", "mkdirp": "^3.0.1", "mocha": "^11.1.0", + "nock": "^14.0.1", "npm-run-all": "^4.1.5", "prettier": "^3.4.2", "rimraf": "^6.0.1", @@ -551,6 +555,19 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/@aws-sdk/client-bedrock-runtime/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@aws-sdk/client-cognito-identity": { "version": "3.699.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.699.0.tgz", @@ -3907,6 +3924,168 @@ "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", "license": "MIT" }, + "node_modules/@lancedb/lancedb": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@lancedb/lancedb/-/lancedb-0.16.0.tgz", + "integrity": "sha512-p7m7MfBDH7+4OrfhCUi/BhcpOiuZi8RDOggLohSTImZWazhGDt/ROi6a+tjTuapSYbK9dqIFjSvjGAKOvgPHEQ==", + "cpu": [ + "x64", + "arm64" + ], + "license": "Apache 2.0", + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "reflect-metadata": "^0.2.2" + }, + "engines": { + "node": ">= 18" + }, + "optionalDependencies": { + "@lancedb/lancedb-darwin-arm64": "0.16.0", + "@lancedb/lancedb-darwin-x64": "0.16.0", + "@lancedb/lancedb-linux-arm64-gnu": "0.16.0", + "@lancedb/lancedb-linux-arm64-musl": "0.16.0", + "@lancedb/lancedb-linux-x64-gnu": "0.16.0", + "@lancedb/lancedb-linux-x64-musl": "0.16.0", + "@lancedb/lancedb-win32-arm64-msvc": "0.16.0", + "@lancedb/lancedb-win32-x64-msvc": "0.16.0" + }, + "peerDependencies": { + "apache-arrow": ">=15.0.0 <=18.1.0" + } + }, + "node_modules/@lancedb/lancedb-darwin-arm64": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@lancedb/lancedb-darwin-arm64/-/lancedb-darwin-arm64-0.16.0.tgz", + "integrity": "sha512-FL70HVAODMcu7SNxNOGK2z7XdWjb2zcFl7AUwK9QnPXds1h1pNT2uCJgrrvpDzPgVMzPEBH2+FTNT8iBvc3n7g==", + "cpu": [ + "arm64" + ], + "license": "Apache 2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 18" + } + }, + "node_modules/@lancedb/lancedb-darwin-x64": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@lancedb/lancedb-darwin-x64/-/lancedb-darwin-x64-0.16.0.tgz", + "integrity": "sha512-q0Tgk30SuhGZhWaSjlM9Gu6Z9zygzicGubJtls51rm0d7lx9qoYP3hUAmCqJgG65uZtwxdZ41FRomKOuuVgSYA==", + "cpu": [ + "x64" + ], + "license": "Apache 2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 18" + } + }, + "node_modules/@lancedb/lancedb-linux-arm64-gnu": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@lancedb/lancedb-linux-arm64-gnu/-/lancedb-linux-arm64-gnu-0.16.0.tgz", + "integrity": "sha512-WHWx65tC13xvXjW6yqDqHO0GHXxxl+yyOalUI/sLGHcEqokI+tJY8CRvkVgwTOPBjVpibz34eSknaKQFKewT4w==", + "cpu": [ + "arm64" + ], + "license": "Apache 2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 18" + } + }, + "node_modules/@lancedb/lancedb-linux-arm64-musl": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@lancedb/lancedb-linux-arm64-musl/-/lancedb-linux-arm64-musl-0.16.0.tgz", + "integrity": "sha512-RiIpWF+aJqgbPAaxkPVO1NuXFNZxVyl4aqif0aA9i7FntSrLAUheDU5TD/J9rD2HGgUxHwbkVb6LGwoq+KDpSw==", + "cpu": [ + "arm64" + ], + "license": "Apache 2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 18" + } + }, + "node_modules/@lancedb/lancedb-linux-x64-gnu": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@lancedb/lancedb-linux-x64-gnu/-/lancedb-linux-x64-gnu-0.16.0.tgz", + "integrity": "sha512-P4UnXgkhODg4Br9QzJJszbkbk4o4HfoC8prn28sH3UqBdOXbUNQz+23OSFj+xFrBMbpUWuhU6N43G9MtS30e8Q==", + "cpu": [ + "x64" + ], + "license": "Apache 2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 18" + } + }, + "node_modules/@lancedb/lancedb-linux-x64-musl": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@lancedb/lancedb-linux-x64-musl/-/lancedb-linux-x64-musl-0.16.0.tgz", + "integrity": "sha512-8O2m7zwL3HdW+OimD7U/itdx/cyOKsP35ssrcy0p6cLEuljDunMqsj66pTggI2fmlu9KxloqYqjwczktB/mZLg==", + "cpu": [ + "x64" + ], + "license": "Apache 2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 18" + } + }, + "node_modules/@lancedb/lancedb-win32-arm64-msvc": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@lancedb/lancedb-win32-arm64-msvc/-/lancedb-win32-arm64-msvc-0.16.0.tgz", + "integrity": "sha512-Kfo7drJX2jNQ3ojH0JTctwwOQeRGyEpAqJFkVN3uRMXyT7RotjCmSAKcLI0gIS1+0HveA5V8Z6wj7c7SNIboWA==", + "cpu": [ + "arm64" + ], + "license": "Apache 2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 18" + } + }, + "node_modules/@lancedb/lancedb-win32-x64-msvc": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@lancedb/lancedb-win32-x64-msvc/-/lancedb-win32-x64-msvc-0.16.0.tgz", + "integrity": "sha512-n7D9tvkE17FLs5Knoj9CL12IxBiLotnQu/qXHhfyLmh7DsTj4/21Jf2trhfD9L3eIKZnIdwUxTaY/hsQrrnDeg==", + "cpu": [ + "x64" + ], + "license": "Apache 2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 18" + } + }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -4077,6 +4256,24 @@ "zod": "^3.23.8" } }, + "node_modules/@mswjs/interceptors": { + "version": "0.37.6", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.6.tgz", + "integrity": "sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@noble/ciphers": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", @@ -4151,6 +4348,31 @@ "node": ">= 8" } }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4777,6 +4999,19 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@smithy/middleware-serde": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.10.tgz", @@ -5854,6 +6089,23 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@swc/helpers/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -5905,6 +6157,20 @@ "resolved": "https://registry.npmjs.org/@types/clone-deep/-/clone-deep-4.0.4.tgz", "integrity": "sha512-vXh6JuuaAha6sqEbJueYdh5zNBPPgG1OYumuz2UvLvriN6ABHDSW8ludREGWJb1MLIzbwZn4q4zUbUCerJTJfA==" }, + "node_modules/@types/command-line-args": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz", + "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/command-line-usage": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/command-line-usage/-/command-line-usage-5.0.4.tgz", + "integrity": "sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg==", + "license": "MIT", + "peer": true + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -6313,6 +6579,7 @@ "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.9.tgz", "integrity": "sha512-vsl5/ueE3Jf0f6XzB0ECHHMsd5A0Yu6StElb8a+XsubZW7kHNAOw4Y3TSSuDzKEpLnJ92nbMy1Zl+KLGCE6NaA==", "dev": true, + "license": "MIT", "dependencies": { "@types/mocha": "^10.0.2", "c8": "^9.1.0", @@ -6623,6 +6890,7 @@ "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", "dev": true, + "license": "MIT", "dependencies": { "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.5", @@ -6786,6 +7054,34 @@ "node": ">= 8" } }, + "node_modules/apache-arrow": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/apache-arrow/-/apache-arrow-18.1.0.tgz", + "integrity": "sha512-v/ShMp57iBnBp4lDgV8Jx3d3Q5/Hac25FWmQ98eMahUiHPXcvwIMKJD0hBIgclm/FCG+LwPkAKtkRO1O/W0YGg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@swc/helpers": "^0.5.11", + "@types/command-line-args": "^5.2.3", + "@types/command-line-usage": "^5.0.4", + "@types/node": "^20.13.0", + "command-line-args": "^5.2.1", + "command-line-usage": "^7.0.1", + "flatbuffers": "^24.3.25", + "json-bignum": "^0.0.3", + "tslib": "^2.6.2" + }, + "bin": { + "arrow2csv": "bin/arrow2csv.js" + } + }, + "node_modules/apache-arrow/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -6797,6 +7093,16 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", @@ -7403,7 +7709,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7415,11 +7720,26 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, "node_modules/chalk/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -7732,6 +8052,58 @@ "node": ">= 0.8" } }, + "node_modules/command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "license": "MIT", + "peer": true, + "dependencies": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/command-line-usage": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz", + "integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "array-back": "^6.2.2", + "chalk-template": "^0.4.0", + "table-layout": "^4.1.0", + "typical": "^7.1.1" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/command-line-usage/node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/command-line-usage/node_modules/typical": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.17" + } + }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -9072,6 +9444,19 @@ "node": ">=8" } }, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -9170,6 +9555,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/flatbuffers": { + "version": "24.12.23", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-24.12.23.tgz", + "integrity": "sha512-dLVCAISd5mhls514keQzmEG6QHmUUsNuWsb4tFafIUwvvgDjXhtfAYSKOzt5SWOy+qByV5pbsDZ+Vb7HUOBEdA==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/flatted": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", @@ -9390,6 +9782,19 @@ "node": ">=14" } }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gcp-metadata": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", @@ -9678,7 +10083,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -10238,6 +10642,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -11366,6 +11777,15 @@ "bignumber.js": "^9.0.0" } }, + "node_modules/json-bignum": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/json-bignum/-/json-bignum-0.0.3.tgz", + "integrity": "sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==", + "peer": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -11396,6 +11816,13 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -11823,6 +12250,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT", + "peer": true + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -12467,6 +12901,21 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node_modules/nock": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.1.tgz", + "integrity": "sha512-IJN4O9pturuRdn60NjQ7YkFt6Rwei7ZKaOwb1tvUIIqTgeD0SDDAX3vrqZD4wcXczeEy/AsUXxpGpP/yHqV7xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mswjs/interceptors": "^0.37.3", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">=18.20.0 <20 || >=20.12.1" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -13008,6 +13457,13 @@ "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", "dev": true }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, "node_modules/p-filter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", @@ -13506,6 +13962,16 @@ "node": ">= 6" } }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/proxy-agent": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", @@ -13550,7 +14016,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "engines": { "node": ">=6" } @@ -13770,6 +14235,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", @@ -14494,6 +14965,13 @@ "bare-events": "^2.2.0" } }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -14785,6 +15263,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/table-layout": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", + "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==", + "license": "MIT", + "peer": true, + "dependencies": { + "array-back": "^6.2.2", + "wordwrapjs": "^5.1.0" + }, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/table-layout/node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.17" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -14948,6 +15450,7 @@ "version": "0.1.12", "resolved": "https://registry.npmjs.org/tree-sitter-wasms/-/tree-sitter-wasms-0.1.12.tgz", "integrity": "sha512-N9Jp+dkB23Ul5Gw0utm+3pvG4km4Fxsi2jmtMFg7ivzwqWPlSyrYQIrOmcX+79taVfcHEA+NzP0hl7vXL8DNUQ==", + "license": "Unlicense", "dependencies": { "tree-sitter-wasms": "^0.1.11" } @@ -15150,6 +15653,16 @@ "node": ">=14.17" } }, + "node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -15276,7 +15789,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -15292,15 +15805,16 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8-to-istanbul": { @@ -15345,9 +15859,10 @@ } }, "node_modules/web-tree-sitter": { - "version": "0.22.6", - "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.22.6.tgz", - "integrity": "sha512-hS87TH71Zd6mGAmYCvlgxeGDjqd9GTeqXNqTT+u0Gs51uIozNIaaq/kUAbV/Zf56jb2ZOyG8BxZs2GG9wbLi6Q==" + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.25.2.tgz", + "integrity": "sha512-QfQdqcxIBDUn221amRY4A/KK6dBL3NjBOEP40QqHlkRBVGbeO0CHg4zpvtVcF4qC+cUefqZF4aSY8BSnnazlUA==", + "license": "MIT" }, "node_modules/webidl-conversions": { "version": "3.0.1", @@ -15666,6 +16181,16 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrapjs": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.0.tgz", + "integrity": "sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.17" + } + }, "node_modules/workerpool": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", diff --git a/package.json b/package.json index 9b9fc5a86b9..9eeb7b28fa5 100644 --- a/package.json +++ b/package.json @@ -286,11 +286,11 @@ "lint-fix": "eslint src --ext ts --fix && npm run lint-fix --prefix webview-ui", "lint-fix-local": "eslint -c .eslintrc.local.json src --ext ts --fix && npm run lint-fix --prefix webview-ui", "package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production", - "pretest": "npm run compile && npm run compile:integration", + "pretest": "npm run compile", "dev": "cd webview-ui && npm run dev", "test": "jest && npm run test:webview", "test:webview": "cd webview-ui && npm run test", - "test:integration": "npm run build && npm run compile:integration && npx dotenvx run -f .env.integration -- node ./out-integration/test/runTest.js", + "test:integration": "npm run build && npm run compile:integration && npx dotenvx run -f .env.integration -- node ./out-integration/runTest.js", "prepare": "husky", "publish:marketplace": "vsce publish && ovsx publish", "publish": "npm run build && changeset publish && npm install --package-lock-only", @@ -308,6 +308,7 @@ "@anthropic-ai/vertex-sdk": "^0.4.1", "@aws-sdk/client-bedrock-runtime": "^3.706.0", "@google/generative-ai": "^0.18.0", + "@lancedb/lancedb": "^0.16.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.0.1", "@types/clone-deep": "^4.0.4", @@ -344,9 +345,11 @@ "string-similarity": "^4.0.4", "strip-ansi": "^7.1.0", "tmp": "^0.2.3", - "tree-sitter-wasms": "^0.1.11", + "tree-sitter-wasms": "^0.1.12", "turndown": "^7.2.0", - "web-tree-sitter": "^0.22.6", + "uri-js": "^4.4.1", + "uuid": "^11.0.5", + "web-tree-sitter": "^0.25.1", "zod": "^3.23.8" }, "devDependencies": { @@ -374,6 +377,7 @@ "lint-staged": "^15.2.11", "mkdirp": "^3.0.1", "mocha": "^11.1.0", + "nock": "^14.0.1", "npm-run-all": "^4.1.5", "prettier": "^3.4.2", "rimraf": "^6.0.1", diff --git a/src/services/code-indexer/__fixtures__/indexFile.json b/src/services/code-indexer/__fixtures__/indexFile.json new file mode 100644 index 00000000000..fcc2ca60be8 --- /dev/null +++ b/src/services/code-indexer/__fixtures__/indexFile.json @@ -0,0 +1,50 @@ +[ + { + "scope": "https://api.openai.com:443", + "method": "POST", + "path": "/v1/embeddings", + "body": { + "model": "text-embedding-ada-002", + "input": [ + "class TestClass:\n def __init__(self):\n pass\n \n def test_method(self):\n return True", + "def __init__(self):\n pass", + "def test_method(self):\n return True", + "def test_function():\n return 'test'" + ] + }, + "status": 200, + "response": [ + "1f8b08000000000000038c5bcbae24c771ddeb2b2e662d1927de11fc15af24913068d8f2c25c18d0cf1bd13db45427da2067318b9aeaaaaccc789c47ccdffff0f5f5edbffef2ef3ffdf5976f3f7c7dfb8f9ffffb976f7fdc6b3ffef9973f7ffbe1eb5ffff0f5f5f5f5f7d7df8f3b7ffacfbffcf4e38f3fffeddf5eb7bffef1e7bffdf8d3ff7cfbe10bff77e51f37fdfaa4fd837f8120243af38fff7c51052665fa8f8b7fda5bb30d59cf8b98884625ddda63a28f674a62d0318f8b88bd96f3fcb556b9363d5227a751cf87a2ad7bc4ce42a330f27c7f8ca9e9f3fd6aee63eaf44da28e994a7a80a9a736dddb122252cfc77efa004ca4673f3f405d7ce27909d9adfe5ca8b858a437dd3ab08ab3a4aaf0a23d952854397f6934b4694d7003c48dd72f55a1c26f93ecc9527b2eb7d232cc9fcf95f0f419a72feb54750a2bcdb26e730eabce10a7855921a73cce214a75355d161b75ada030ca9a088e77b7ce8ce7ad8636570a2da8499b500e69a4c93386506a13199c446215d167c733adbdc0975b231b27689159456193aa9b38bcb92afc73dfd4e6a0db0333a198f3d9f4e235a9644687f11768076a283a2447d2f864c45189e72e9a443938e925ddfe390e5f8795812eae0486e849e152d291793670043dbcd9621a255c216cebc6a451de34c2e9c0dbbb86321c630e0cbf49113e94339aa6a34395a8a45ce2f976d34a9da14204848bd2cf01201db42884fb38554df8a85b3cf7da4c3aca8b16e5d656a73a4e599c939e8a4c2e24356293ca4fe00aff7a8018a4f8d6d0a8310a019342f2ef4b35e71c80b4c1fc568c4f118050697a6aa66980b7c0b7100df74d83860e872b50460f6d912d25a76e678f259552730c7ccefa0b66dc641a254e2190aa3db4004d554b8a8a5115192e2b1336e17ada46c05da917ccb6c3895345a626b99f9b8b25ef9576ba28b866ab6a2675eeed8812d35c9c7c727b2dc394aaa442886a47de32c2dffb5a80c59e379f81b47b06372eeb6cea25592255b4dfe991ade0466f6a5207fd4425a2fbb4983d5e8e989accc6e9869d4239afb31947d54d4d4c35cfbec2478c2bd1b648e74a9e9a05da2c8fea3e79acaa66a3e7bc179741b873cb86e873056e592174b05e11c31713a80c7072598fde3c12cf0394dafdb47885bb356de07e514430a0f1c1410e81d66e0280b0e9c92edaeb746f219ca5ae0b77f9ac06a98f52fc3a96ea509bdfa86eafea84e9114ee38e04032774978870044b9728177d190b1b4e424cc5e6db09374f99e202bb0023507c151596c939f7892f4894d605c1b9d9119445904802855bccd50e4e4a5371102052774d0649cbcab49ca19b148a234eb20b46d04967fb2428e03477bf8a02deaa22825b1c2a5d922897c858c2383a75411d2373043c230e9573a91806d1856ccfb30615db707e7e5a9437a44fddb1183d3bee0ec3890e470d871d66c4a37f8be1be56db5d4119d616882482a78a3add7bba9ad9d12c8de04a8608e9f61b4631dd46792f6a1ec118783fdea4a9012b96a572e55ff8c495db640f971893a4b5f83cb72a6dd116c33a9d3d41e16aa08b62b96ea7b815155edbaa49bcdd043254214f127def04a9e7f03dad920f0ab3b9c15d071ef0a05b4b2c244f199a00ad1dea706b6ed1088f31c64f1691565461ad261bdccd0f7278e3914955462e89f1023315497fe83bef668e68ae37be9c92b37ddce61e74b8945044c974c4307c84476e5853c5db0c188608533ec9b992a1e3cde7aa4ba395236dd594be454c52a9ef6bac4042c197a579fb5345e183c422cb1578a95e7876f3ef71a9ead6fcb21a3974bfbcca9224268c8cd7a90b6ea1d3c5c579bfeb74b24d2c65c120148f0af2eefc4810f23071890393bd5071f0a04c491d89079876bd8860bb4e075fd6889a2802eb8bb65953949436c669ab8eb41bd7bb302d9604256638b4d136c1f982f4c5c91c1a88e02010ed7019fa7da25a873705d09c1aee199757bcd5856da5cc2b247c2b7170749b37079766f7534a787f44d75361795f358f1ed6e496088f732ddaae679c20ed3ad284481c514902f4228c5116338e02fc7eff4c5bd38663a46bfa66d287f8949e42d3e9c2ba7d459193f96daca7412cd58af886bc9837533ee9f4246eb535826551b88b5e0df6931a623053218dc92206c21428b31fcbfc1e72bd827f9c931def44d057f9f210ce235b32de7ec2488c7be7023a593eeb47b4369f3a5731ae7ee8fc84d8a1fe1d5d25b4b565edddccb056022b3d25710c38105443f674188048555246db8a2a4582027cf9c26538a576e5d2f255c118c2daea14cab1e01e6b15508e88c7032dbf93713b1b87b2abaa0cb10e98871835419996645d4a6b191d9fb84a38b8d969c4324ffd4db9ed63b379977f4575ce11e1accd199d5807a6ec3027b56c1596e19035cc12615e5547a65f2d7b3007cccd088366bc10f21493e08991cd35de8aa5857dd538ddfe74f4c48bddb0c891d519899450e6f61a4ba319a74ea70c5706a9d58c8e0e811cbed89d7e04936d2cf3c1198c4a3d22e132b223738acf7a3e2749a2c2595486694fc8955fc73069f61b9ff6ce934165e5ef091cacc137a747eac69371aa9a2e333bdcd67a6c0eb6ff20026bf62a2d076f9a551bc39792cea14337530d3b807d936d580b88a5510ca25563f0c0a0bf82922ee60c2612dc7942db2dd8a3fb48d9f6b8d2ceab605621479cd7160f228261e9c3a6916e241817e7b527ac58d97dc598dc02b076d6d67d3fa24ee168d985be998ed218eb530ac5cd8e3e8d4aabb820a046334e39d5d2350588d3692d626135ba4bfc7872d5ae1702c41ca70e293e2c1a8a4d95c8154fdc9b012abc2457c0e7b785a81777f08f3d512c514a480893ea626c215a6a92d5b1ec2bd9ec92b28e5096151085233a9e8c7abd7e5c18e66f0c94f116ae969dce70fe2354b8e6f477f2149d6759b50ea4105dde744c4ff6fb6ceba5f34445204cf2e23095abe3a597e7a1b66bdd0b2b9c9fbd215be5f692a05ef4cd8ed1da784cd965ebcab03d2a860e02827b5a55c5e2dc3ab19726e80ce412b05ec0c5d5b2234fdf44be1c2be639f0d6e7025cc306d735ff68f7983dd5a17767896ab015ab4bf7c30f63cfe63ef832989581ac3ba686a3bb1350c29ba68a16ce638bad5a74541586f6633a5b57e4610e1a01768bc4d4cad03c69923a0c067415fd2b70f1b0cfffc730ae91fa56ade0d5275ada560d62b6ad3a33c5a313e695471ed140881c6766a5dc3ac425cda5ef1096b404bb02a70fbf014a09ebffa21989ab83a08ce67a54b79ab1e0e29804abb18d53f4aeddf766682b9a53bbf6717409fb2c3b44769a6fe7641edcb77b1af4aecf02b9cc783ba3239db175d919bea6d6f9aead03283b04da278b273a02ebc673e380a55216bb2cb6620964fb3473b6b1cc87b3f9dde912079756dba9a4397e234b30efaf5a1168825d8ecc658de768f5a9a8bfb7b002c71d5c1f752d81535e1173482ab2d2a6885dbd14046188f8a1f97df74a2aea204774ac7b7cfd3d5f4f9865fcaecc02f7998cb551a887eb40497d145fb59bed26eca9e5a37abec972a592b3f332f5555950bbf0ee2d0aa259d514592587dc7f5866827db83db4a35b41ba5c8701fc4ebded002113d78aebed848cb2ff6e5323a7a723e147174e681de3d27d6118032e76f1de0333ad07ecc86202e7618b90f5425997d899ce2156b3729c5e6f51e13b61c4c5a0bbcfd0d367d9eb0e6b7e945f5f8badd5494f367dda6cc4caaa9c35eb399a314257c444b1f6a0d10d1e27db3f12167a065975147dac2b8b6d0ec7298e1db63c4d1059ab4d36076e69d7115c23c78375aa4fa320d7d57ab3b0b549ea0cd9c46cf81877ad68bfcc283aa294859c7acd2571db1fd73a73beb5bfa7305f8cd5098e5d8f2cfb7d73b3515dc75af6e992cb5bb1f39d6ae7ee4a169e7f2516ce9876a4cfc8a23a6eb55db96f672e8307297be7929fa5a2a5ce8081da0c2b181e26c6866149d8a771b395509c87aa4378787d5de0c51eec837685d875976507c80ff38fdc0174ee2ce35bd9586413eff9008a579d6267eb0ca07f1fb1cc411ea000f69757b2853b5720ebe5e7761eda7a4652b0a3e90063ddd05e5192e9ca061d232d6f4b4f567175e7ddb991ea121e96655c16d5ddff2f20ddfcb50851b3634586ebe429b8b6f3b067c874ddbde04440e7ce26d1cbdaa02a792617c5d69ee2492a241bc29a22357a661a52dd8f43dad3e63812ea8a894cbb3ecb3ab25d3b594dff3cc1ad997346626737dd790c5e66bfe34c15d799a7f4e99dfc3be3b7e122d1bf2717a4ed402fc969b5ff050000ffff8c9d5b7224b90e4357d437243e4472ff1bbb81cc9a8f02e4b07f277aec72961e2471801cee242e7fea1e47c94f3b1f33d2081ec8f62c263f5718c82cd91e3bf10078388effdd9acbacc8dd9b7aae7bbd8c21055c2a21048d2f5db4936012e30fc5db55dfdbb56df38d60674738ff50b3814e2327479ec383b9d5e32bd91164cf75cd7af2465dcd1cbdedd8d16207019c2f27aaa1ae4e9e5e74cc4953a5a8e7c8082cf1ddd6921eaf5040f1f2bcb5c48e22498488060a964ce9f9f696d63d135bc4459e3ca7446a37a8555bd681972bcabe639d49e645cfb20cbd70412d30de8063ca78a88d7989f9369ee766668728c882c47df43daf2de036586cd7066103e09251daae71de0f3a35fbf4f6e75c28710b0262d69930e759e0b63eab4879790a81657ca6b445a5d46e8f8cc69587ed4cc01b3c8b8b93264365a8bffc61bdab7c043d6f00f8bcc936682d96060133b3c683b1b8f16fba0d082dbc42f5dc1cdf475a83396ee722d5ab5bc5ea2181d95d95fb88bf4ce88ef79cc472d1da1733a362504734bd773c04a0991b248fee613d8a71c1f76b5db6791cbc7df96c51c95659f1b3529def6d56a64e28f41c865b4494d88810ce0a3379257fc8dbf3ce098e07c13cf79f0954f484f44a8e1d2757e046b5b27870d73365297062817ee26d54b67101a85f27c4d3894713d1ccb8748439ede4ebf8dcaaa34589be7abe32268bc7717b8008c9417f5d459dddecc47a78db3e6a25abeecdc5e446cb2c5ac54d04de07d4b4eb231c6e8136dc9719e70f44d5ce342b96dc3ae11c9472c12a83b7610069e2c9694127e10e0eb34f97b2dbfc1c02617d1f3bbc86714331da0ca93addb9e8defe5870e478c3b0eef4fe0d9d78376776a729c8396362fb0a8333a2f9788a35acc1a02dac92d6c1eab4f3751453655cadec6ddda5fd84384adf6f0126589ad2a8baf64c07d09789382f78c11db178ba045c7d9b9bc533df55e75b79a331631876c39328b55d844bb9f4a98bfc0fe46bc7c5fc3019dc806eccd0040fbd79ce80e79ad8cd0d1aff6215cda1c1f9afb2c0fb414f6fc6682de770870551d0597f58a7716f99582cc13db7b6837e419d761da9eedba656296f4a72fcfb636dfc8bf9fdd4c6fe3d3efc3132002a7b961f26c2c1757207df30bfceaf06bb8f93fee0e210ab0b06c482f81436a81c317e96dc874f09bb19a1bf13e82b77d95219864b95671f361e8f8cce4f2e6515c045895906b62d6bf1ab2c305f5483c1c32c90f1f2939894f1b3991321f6456bdb1d2c453164fcc231352e0f113de15ac2f364cdb1e2f1a379fa6e1650d039b170ea4b817f9bb3ba99c06af83fb904433be54bc7ff8da687c7ec51dba6fd572ce1a37e46b5ebefa3aafddd66153a9d4e7034a2aa8601ae171d2b8e077fdc3da7837fac45ee09e9a10d5dde302b5935a16e3f5b61e562930fdb2257381a639ed2e44631cc762e1932bd180a6443e1ac0674813caf0de08207ae6ed95bb5d2efcbe23dd6ad45f3421dbb4d24a3e35e5a1b78a3e1979e367bec0cf72eaccbbe4377c8a1e2d5ef6747722939735807427d1d9b9df6629e7f0511a0105b8744e4b87c611edba59cf42cd55916a212f6e2e099ab45187540ca5e80c6881fc1d4431df3948ba8e130e5d2f3607cc553935b16c83d91053ada3911627d05926ceced3a3d5ba168e8a49e3a7a87e9ac2420a3ba847fbe56aa302fd8d69a32d1a1961a5618e1797b594b13de2ba3723637c35dee52ef3b2e2ea98b1bd0a81a8c0c1d5f880723260f3bf5eef672771f6df2c37af37d7c431721efa11964213998d5c0d93e290dbaa4547cbae65a2e7e023778f2c43deba7c2e4fb7db25ac6b8954a87eecb7bd5bb42f9e967ae2e07b9b4b3af3da573b9e41118c6cfb201718a0e2f697b3671f36e3f9ec38efa15b6432653c7b6053be297e77390281dbb528cdb06ff558b97e5c98a32516df0857264063854ce1db288f2cd55c519ac504684ba3186915a63be71aecfc964dd828b1b9a55593808ed590c86a487f752653341600900072727f3f1d704a88dd683bec70de69e0db61b43019ec4fe04f827e226788f9e0469268e3da864f6a7bc0ca85ebdc5fff5e2413c5a4050920e6caadcf99818d34efb9a49e0309d8fc82089ac2cbecbca6b9fe2060c47fb76d6551b330fd62f1380b4739ecd54cf2c9136ea4cc947c8334b4e3003cdc07f839f35b6f994994a970e1202b029947e9f4c20de62b4d50a7c3011e317121ff2706cd91ab7b54667a2d67ae7f080e5996027dc9b12eb53b80a682d16f613319790c7f910370486cd91a93c9a761e98f4e0a8e63f0ab2152f4e685fce73108baaa57519b29ab6e05c97699e0e2edfe328a61592ae49cfef29a97e851fa9b47b441639fbf8e62c39a4078da03ad38b1fe015847ce6cc4bd27f2206111fce2e44d4a692c174402c898e5f694b12eae664a9b27de0366a1edb14fa6ae54b3378092ed037519709571d919cf6dc8ef995c813116772b67fbbf8de030d90c516ca14049fe010514039e3371df985a2abd098ca4e3acb6ef8736fa1856fa6ed442004c39559e9ce3329186d8df3162523e2f53cc29eb9f9669fbc8c6220b6c6e84af25e3429bcf1560f939b33299fa06bc55fa2b036f263344ecc1ca66405e2ae9e208cd19798ce5706bac7d48e6a35e21165c4a1c5bdb9afe0c27c176e77dee80b594c5cd8033c1b4d26742cb0c3f9216512d6b1f1101753401b8b56ec7f9d01fa935ceb0e6a5c3cdbb7bdfb5386dd89f12d217266bbf792ac86e9949f708b46f14689201f61908ba1e6134056925f1567b758a306963e3eaa30415ce2155a0f60353a37117ef5733d432065d9dc7b8e49d857cdb8e85a70da5689f08cf3ba4234818e6c9d329c42f48b649e41ae91c8d4bb9167d2509db20978ef543623d6a4974869405515bcb69a27ca54b210f67018ed35a0156976e929b5861004ff7e6e0720fb9c38929869e719818adbdb5c083b5b13a9695403e185d89959255a860119a06f679d638b8706708e8ef805a261a5d17050607b3c5aaa8c47a362e238f6f993e512f1791c8e73cf4e76a80031e2b046152beeb39bb3419d2b6f9b05555d4c22b117cb41d87e58f8a63e97aced75690069b6fdf6c6551d12d66bd03b2f5a8003f067c3d7b44d6af5bfe50ebe461622cd35b8147bc2574f4978c48e588731f1e703b08a8b671322e463258eb885aeebbb6666cbd6ed6da09d7885279e027fbf8c0cff579fad160f8e4a42efa5dc38f3e49639eb869da17d90fe18b36d4c81044044bc80183a075d8808f2074c1d83da8621708814382d2ea02c5bb23e1089719422ba392a20f26ced1885fef8f43bc738c5140d40abb7f7a01412201bf7ea915cd039e878b843ce899d4ca76675ca1c4ea0b34faa0522ba9ab1391b2edfaf4e51bd46ee0ddb074e82c4ca1ea600862ffd0e327d423e6ac06ff3db54eadffadf3ec8c2e3990eb41056d90da29e26cff4813580d95680324ab04025671391a1a2976ee9412d5d66c49c92fdd367c5c8d5bef3f15e1d6090a92105c5f46abe9c36c25d5cb7177b35ef71499f4e7242150e284ac30eb78db6826db86b02b9909487731e1551029991b8c2dfc235904d12b6def31745a37009e16a8f7bec9a92b338a78e4ccaef5acadde1ae2df64b530388938bad71d60a0fed073a26cfa351d1f12187918a64799d7cc82be14729d3f933bd4ab9f7711636fe06595e51fc6c1f61e43090273cf6db33d75932a5983d8806fc9d66f3f1230405ccf0cf80953e14484d21b7af099c2b01209cdf4c142f019c2ea3136b038a4305e9ad4a039359029ea1572efe54fb2132b9488b84032099bec7d09c99a1ab4df30e8e03329f9d5211de2e94854c3e164aae5afe063bc9a1b3cb1bb67371440ef24a794e757648aec73526f8aa57614c657b29cdb0b374bdeff22449c6f0e6896ec97841954b834dc7699ecb7f11283fd764e34896483ee46dc42ff683f7240a13fc10a91a7d16b79de6c006d8d5e0b0470b195c916ae9fac4bb2fb5b9c74a76edf4f6e21628c690726c3c2d00802978b5cf3979c4e173354fe137c948efe66334a48d48eee96ad4a6978b0e4f32ffe2e1786641cec1310ad0bd92b285a4845e2f6b248505276c1af0135db4982a982539aaae8ed6eda0836879dfd03325f85e4d7aa7a8d74812ed90fa3c6a89bb73a3806806a61152245fb6bec8e6dff58cfb1c5cbe3df99d1498b6624445c5c646e081eee6e372a35d9522345a275b07e74fe7cf22fc1c979c3b84c66c49b36fbcd4401abb8dcb87903c4cd88457bdc94c175ef5153a90c3c86b0b513417eefca8f480d703388fbbe805459fc3e8c9b693983fcce6b51709078d2e195680c694f2c33296aa24ca4ca2aafde4e27ecad163c8eab4014fad4e871c0bedc8909bcead032ce898e2fe764a7e96dc20ab95275dd032b9237b74df966b05b509a76bf18b6cdeb74a54090a8b39d9e8eb1380e8497cbfefbd5b346e244bca1b281c89c55499dcf53e0f4cba4412baa47041096ef1863a7a27c986becccf623c53a29d3d4ecbfd09e87bbbe2b29754be4a98c0647c892a7ac98b74604b712e372939e115d823ca1870c7b0324bcaf879de0ac1092448fc96b7f32042907f2ac2afbea1d67f3f95f196311a0cf0bcd3e1c8a81ec19c211dd28dfa4312bfb1b87e61eb3ee2dde174797d15d32bc346d7e19b07cf5516bb94fcef8911694c27d971901af1079ffaca363ddf3004eeba64675ef817e037784d97a46ae0e252927a83439619eca7e613ce010588bccce69a16856ce1f2d100ddb6e44bc5f0cb2467ee6e45f535dbbfa204de7f0a258736e955b3d9a838d856698f2b9143e11b2e14c18871ad45ff295bf160da90f58b9ef5f9da4ba3775657f551c3ea4dce328f4a4e678541385ce61008a3ac517a3cb78d82f92c76e0bf2251cd874d0bd1f1c8e1bc7bfadb9cffeec83ad6a238dd98df6b17abe915eff3daa3f19d48befd7a91d41ba18eb2432a8675aa59228069c21079ccc7edc4d77b89fe0f0000ffff8c9dcb8a654976447fa5a97982bb6f7fedfa15a149ab0ad102b5261a0884fe5d989f134261cba323460541919971ef39feb06db6ecb9efc60616f42429e6e414f5d23b577b93cbd39fef3d629113eebfc393f3699f11ec8fcb734effa72acd52fcfd3c0f67c16aafef2b2fbe4d61372120e2fef3a54234dbac3e5c5bf378d570941b3970364ae19a101b0e2de1f4b15c3d5a53114087bba426226841d2e38d81aacc0415ad8e723fec009d51dd251ed2bd358fc8d63cb3ef9f4d696fd62d0667df904daca49c237a71ff0672fa0eb07422261439d7a20944da1b4a49687c7d765d0d4fc9039b227040361f7b2542c16a8bc393a0a54ba06f645f5c8e7f9dba9a3226eeed3d02d7933b2ebde944e30931a163f6e722bf9739a28fd36495de7771ba89247632ae6fc32692d55fe15d94c28a4f57533484a0423095c18571f58e5b53ad4abae17a530f7edc05c61d7de2dab5d4c8e9626a686df50b56d703494af00d03f7618122005c2131bb0b0ce1009a3dff4384ab82a6d29a3b59ec20350e76ce39168c0343e45d6f0d398f0c727a35a3761ccce83dfc078f688d8c451124a7da9710a9bffd127762110cdacf1bacb9153a2b96f20e7bff044bc3b6a6c728540e3ad48d7ba249819377c967349986637c67617d9ffcdc28d1210df3d7d79dd0b7cea78b62f2383096888df1bd03e39a0fa97b28925e1978588d01fed6351d80352b3455c4a942adabec94d380c8d53b24dbdf07ecb38efff5145d1ae1b9333348595027b5cb682520d0448329b1cc11191b91fb2534a227399dccfb5c63570c9c84b5412593206d75962af72e272b3eda1a8a754f18d8778fa8df495a5ffa428e07755ca8e85e03f192ce2b6992632860e1f6ed36a1c568a48647430b5bf766c2b5755c01d265c863689b5eaab5337f8474a9a738d3f56279f046a3a32c8efce6d16c85e359b725a49fc3949f247f457e0ea187575ed6af07778d6648180c0a4f54e166122501a56f5c979e83c240b9dbc111674555c19ab5797ee846d9d442b35911949fedfa2fefa717c2d940dcb9113dde7f69ee8ec1c9d6b4cee33c17761561f3bf2e6ee6af63ce5b603a9f6829089be8c45d427d91e9d866ba53559f7d42ef7e2b9e80818a9ede4e79201f0198f2658d1d679645a3bdf7fd3ce69dd5113769c786e5a74769418d928d5ca773c7fbc37f3efffd9fe7fff9eff787bffdc75fffedcf7ff9cfdf7effcb6f7ffefb5ffffce38fbffdfd5f7ffbf8637efbdbdffff8f3bf7efbfd2ffff7ebfebfffe9f7bffc935ba97579834d5c87dd8ad5b7b7bdc2a7674d4fecf6874bfa7ef34f47902f85b5dd8cd00634b11407d82d21cae716d0a035e2ff1c8e7d4c6b9b9fad00b3d39384524c3adeedd9c6463b8afc657b02a052b598b9c1543b95c8793ed43bedebee06c80a2489f0e3182aaaefd8a3ccf2bdf744d0fc20efc97cee2afde50eac0960f5ddbe96ea11cdba87064dfe804bf8f223408999e55225702c90ceca941a4f8bdff3afc5b3212a2300706a10eb5427947a0e17d4644844325841e80957a43432b558fa44402dba38ec77e5ebdc50a82a01af38685bc1494c36572cf894dbd4a8c92fc41a8eba4b400edfd52160cacbbb827da52aaf860a3c8b0ef09e75ea72b3d9e3bd4bbfacd575446b648dcf35159ff1a16d5f1b947b4d1514ad720171ed4ad93b975ca8a8972d31702f48557d31f892057a929c8285dfaeee40ae890d39e730a1defb30427deadb86df4014ca485c82ce1fa060d6001249d2ee2dfd69db788c5dd1163ed6b977dad6dec62af0fbcfa152031ca3ca6ad30f0cb5e5e47c57cff1ae282ad319179a9e2c661513474d3213afc28e748a7b6694ed3b857cfd1a59113f149e56161d67d07add736c28e67a3655b1076a769ff2a03b755a7436e012b24167a8f2d67684e1bba86570480f1d657c763263f8dfae6648077957159e98e348557e15d4711d37c77619ffb6d3ea58200edbc47aac950beacbae9ed5a94b5c68e7acb42e7612ee02e292c2d1d1ce24056b7fd6c618b9eab755c3e902ac2c5e03d4b835615e5475f1ad4895eb50d5de011c88b42679e75ce11c2250386150ef1006916566eeea363d1de52e9c24b19e2b042f951488088e85afb95ba44a1bf4968596a91d0c609810360482f58ed45be34f620f04be251fa59f9e45326bc421ae4c047dda98ab922f34bb66a6f6dacb92516987538ecb83ec937dd16792d019826cab243a258a76a9c47a5ed64815f83af828fb2698591f22833772f72ca4958b53b4bb7694890ba4de854cdc6b63ee58e88c2fa759d3e34a3aa0392077c81a8113b5fa5236dafab4f4737c74b8f66ad0253956df24dc0dda420212418943f344e9564ad5a186a4616640a86d85edb1414876897608d12e30c6765c4315b53ad062a4dca76fd8551b8bf7630b1b92387f676641de59e2d5f6c4821443a00396da035d020c9915e0e3e86316644e421a810b2c6a359d98ee2a29502b91f07a19c0e097ac0f93b11886881544a5535fe3bb32cbc5ebdff054c846928bfd856288052864d1747bc7cea0a671af045fda5adc81971b9ed9b60e4b0d188f0de0a3fc5dade37caaa1a017a4d59377e058b0c992e4d2cfac4e100bb5214d5b09be38309c96409825a7f0307c8d952a68c98812c4e1a81217e0d0913209434264ce981e7dac55e6695e2243a040f8b4d6a88cbac913462ef42e9f33d50f82517c3a9fd6285d33e8de1699db41417253a1c02514d24acc750afbd8a4e65428e1da4235b27221772fbaf26e12c949f5c1162fb4dcdc9e63d1791a634e0da16bc560f8247fe6c4e85fc35a479e68d1e4f4a38fec0e7bac5ba7200c09866a922b322f3d0378f12999c795e56cb5c2a05cce4fe19e508d01412c65cee5f0a0d313678b430f98c1fbbe94578bdd56278e764aa604b7ceb6e5aa41b0714e70408f49bba9d3ce17690dc790281b5d916be7eb553db26e9659ab717222757bf54ea4e21a347fd7a9776c8200b9d50fec5ecc396b07e033d5ac88d475e46c93bc3bf946dd60eb3bd5c791bc7ac7f3c130efcfbfda13311f759720a7b52ec04895dc01a8ad87106efe3d08e9bafcca5c540b33a9f36c25f727aa78faccc908b161ca9eb385ce06f3bb15e1d7f590f8516ed3f1ef3ae76f5f11141788ed28c1361ba8fc1acdb9c221573f04c043222d80944bd5db3ecf5511a2a6d83eaa99f289c0bb35b530c7a55abc7903a05ec8855cd4ed325cebda8cd26f318ce011d95b045e3f9f652beebdd55d7c79302d4ed41bb4ffab90cb03cb7b0cd1eae3f26e2a649a7808f3d23bd9e77112f9fbd1720e8053e56d28398866588503f8aef24d1f9c4a2560499d849b82eb801ae2703faeb160adef228734383e5567e253825dd100ac557d92ee21450662faec0ae442635282ccee1df2a72cb7d95571f549093a6552549765034547b8064589ab4309a2ac71457ad1549f27e4af9ea62a0d2f26d6ccd0a768ea3f0804bd04852905d6e9d1a8d957b59af9e31a09c944bf662541559ee3e628b7b2a664176cc1b28a3a857ad65ada62e3c58885a89c5af8102d291a3379ba333b8e6682c68db9507b28eb223ae45a3b2e6f1c17d619805df09af01289c6e7717b917f93b1768d72e9f8cf3abb2f5ab1e448f87e1af2e1b8695141f15aadd111de0527a824dece3e1bbd5ef2177700a4ce4065e39426a3f8c5e8a881b0430eb6181ec0f2e9c2bc69e553f708ca3585e22fa8a6c998f8a1065815c2b7b4f459962fbed242099593445e92d89506f7d3e938003e4ef2d70440aab51d1d1d305dd678e81e5a2bbda750a61b59127d49d2e20b33cd508a8af155150a12db9f022cf95ad17b9dc89ca9bda3630168435e4bccaf7554f6638b32c38b20264c0abe3c8b29e1b1a6c14a6345d64b40bb95e59ee6eb88b46ad45458bb3c36679142192d37007489d4e5e22b15eed4b7662599c287059aec0cc61b76b956b66c24bcd7f1aa42082dcbdb49955a93efa37f7f4012c02886f310a63e5657676f7fbd4a4d46f13ec2eb88fa766c3fd3681eda74f5a80359e5362ab2a755bbf5a86c935d5117316c3e6dfaf5117184e1bfe6aed3ebe222524fe786f76764a763798fb5dc121b6d05ea0d84fac84166dce2f3d2626ef69f4d11f190ca55712c23f6aa4271c3b21a0ac32d8442de7437679faee3011af428be37a702f6987d2876c9922d31fd135a672aa092e42150b9d186ab998acbad278ae1f6873a8b2b34a1670b179fd1cfb0c85e82a9860b9f7549a6c32c5d36965a80a6590a6041c41eac4f3bada20d93bd55e70e06cbd7cab9b19bf433c1f249d10c378f751946dd652653f8f65dbeacee8cb9db99bf9e7c67e789f9a2fcb5ae7b3306475d96368802433dbee88e17bd2b0ab4bbab5a2c7d0892cd68929efcd654f6c5ead79b9a11fdbd48c1208d38d4f66e903546b408a441d5d1d5489bd96a7429ec8f59bbc048db857d739e38675f4fd47bce641548e844d1015e5204bb7b6ca76d0d001d342b8f34dc1bd98410f3cf26cb76bb7e285456087e6d63239572bfc2eb6faac3c92a9f4f5f0feba4a0cba074b9c403e13b1c2bcf2728933bbaf2ce9b35fcac1821908053c94471219945220e29a87a34073335b93fdf145faf4f49244cc5f46029c629dc1d8ea1132703aa423ff31cb88dec5a7a3789080458ca8fb4b933b32f1ead57ab41389e57acb9e2f39dd90565c5853076eb13137721c7e9fe99c0e40bd43f366691bb362f72564d004b4de6b8581344dcc5e65c547ee40ef7dae46774daa6d0fdbd81835815ec5aee48594d7e1a2265eb5c1527fdfb6a2c1d133d677d8e01be7c1703677df7b49f4d678d70748cfe490977b5206d950bd338f573802376b9296801db80009e448577c49e865548c9f28663b149598aec84309b96307f5ab4ae3ab0f83a6858d14af50fcfe7752fda49c9797719c9995f70ead4849855cd6b79e22ad5cac156e8c34f304d4e313f982a5216270fffe2c8f38e26576780fae036207937bd2bfe50ab0d82148dad603633bd9767b24828f3039e6ccadd2389e510d73624dceb45579b4b039c630fdda9ccc7131246833539b1747cf67383006376c213f35484608c2c775905d00ef99c9b3b9d75a141ddc7cd6c58d47a3b06fa83f6e7d3e473c21402dffb4695959e98bb8814e1e5aa4ad88ece695bd778d35ee1a189d6e63824645b5a3f10e2eb9ac3f32de748fff95afde8a8b2b0b83151e8a386db9b50201b28d1160a7041b21f7988e488706d569bc8761c5e36c6cdf0dd9e8fe8e241495111fdbb1d226b424b1765d5a568a581fb7458d4ca35162d71f72be83acff265c851872f5dea1798005ec9ef8ef951d9336b52d4130310012209931370a988e17d1f3ba6ece64eb5a8c272bbe8a3aa4548abada90e070f4dcbb106686c22f381e0a4a7c0e7f752389893d4e6df1255789e7279583b5d37118c9dbbfaa2e07b1afd5653db1a2c2b39ca67dbd29b2f6e0378cc830e806b49ab6f94f6b34c8b8e2f6330d629f10a67d8a64a781e16e402878e9ba2ba71cb3a1d297ee8ab4375899da179e5ee1ca9390b4ee153376873146996bca3fa9da9ea75ec3f1a458efa14290190d4672ff913f9e076ea962292d5cfc22b2b28dbad0bbd808f65af9ead72e635523d0ffe19ca2f8063ebdd73ae394c29f87543f6c2f9bd04a421b9a4e0c1505e63318f921798dc5505db69b98c2d1c809f73b49713aca3cece02fa936ae5c078bcc42404b3cfee425c177bfa72d5b88f5bd4bbb5d1fdd7b31231d0227a33d3de4109e28ca1c3e76e0b0089aeca049c52c4f0f2c52642830a0cefa2703a157b57361ea8e7106e2b21a268f8527a25dc10a31a92da31013ee8a88ebe90a2225db846346e6dfc29d5b82a0cfb458711b0bc3a32acf71a6bc03fa4c999b79776c1cddc80a4aa81de6d10155d7374d7f4e4106c4e848069e2e585091a8f5e0241f6268a0f25dac0a428dbc0e0a667b2fef353753c6d3c07018dac0e6153fc9c24e653851f5e9b8e827215a19d420f36edfbd739d60792b7055ee7aa3c3751c9b37d0eec5fafed8f5abd01fad3ad6bcdf8d637f0d102de2b0f1f45c881094fbfc0844899090f0b3f8356caf1dd50f6f9c27a5c78d743a42fe8b2bba12f4e8e12bde7ae7dc84f8068a40a3f07eb2904a942a6ed90efc2dbf12ed7e7d21f680a72d5c329443af3b7b1b9e3c91d8dc5fe28a55e232a84e2da08a30a2c098aa48c7f1d732b6d4b1bd8d5abd8c0cbc8b36d2b45bef0e6de52bd027e6e674eb6a6996cfc24eba683bcae187ea5134b93d3ee96735f1e106426ef5392d712b17431b549d19c72d4a12da90b2be8b2b3eac56db51d3d4ad0d4aec56ea1ba2a1f9c1b46d66ab81870cd4c1695c89fb47d270b050012b54ccadccd84cf6e5587e2d471faa2f03b9c062f6f678829e10d9e97d4e505f60dddbedc9458e53f9f8e81bca98935571648474a2f252e0e0a92eca4a1cafcb5af1cda81806faab7e9857d807bee8dc1f77d50a26618bc7b7224a373fce2a0949853fcfbea6d4cff5064dfb99ca07de8ff61871e7dc38d51724368fe2228d5e5c1af38859b76fad579ae1cea00fa96e5a8dc98a4abb47d39ab4449a0e9329fae94716990cc9c3a0eb8d679be5c3f63ad8346b1f7337a63bbba90a30df63911ee1692f443a0043f0de9c80163fd67d3f5fb47aec51c7ea424605c376215929a1047faf51ec0733576e05c1c7c8a691d82d177ee955fd7f5f00579d63511a4d788b50ef0cbfc46faaa673248b18d6d611feca7d0b6a1e4fe34c7f98ada4e353be4b7a63b3c034c4d8b5a60a8bb47360e7964077318ef885d092553bbe6c4295835ee8bc88a2967b85ba1ea5a7e8957f2a24f77a5dfd3f0c2d02f78a99ae0741edcef7a1e50c0d1b32ddc9471247ae3be6df9f758d5eebc3c675ac5a10f7c006d7674b1e9779a3ee31c99c549d9bbf6e1f0a32ac517534fccfaefac8a47bb13291657c1d94662e9297d9d2598715d7db50e54dd3bfa46fa25ea92048aabcc6aa703c3e5f03ed032a78b3af9d173e6c454562b6019b8129ba9fc23e6bae5828cef76cce78b1c0378fbfbbaaa34f98e0b2df656ebf766628122176cad7d6fd5ab53193bdf8897c4ab8591773ced452ed0c856e74781e7648cab88dced1710f00ebcf97d8917bbc91e9b973f60cfc80953cda1e3fa2cf0a2fedf1fc654abf5827d6c4876f129d655968cae170d3737dde7b0dc6b63f03ce07ddc238f86cfedc109785ddc534e76f312a82fd165c615a34f6f61bc07abf4f555f7467ec160106f622eca1de35289db058d28ec0f153a115f57b631e17ad7066d29e0af5ceb63ea67ee02ab39d134aabb12fa7fbf582b152bf608d425f2fcdcce828f8602d363db5ed765aa70d76aab2b0b9e3799b814d4a0e7503d500ddddd8234838aadac26ce5453e33524dc1420ed1e4666b2e8110f73b6018edd5d65eb43a15f28b3c76e05b2a67cca38fbecaac244372b8c05a3b5a60605a84c49db75aeef693d8789f37926fd1238d7daa07a0979d1b66fc6a3cc913431d5818e5d7928e602b178a8a0de1fb07b58732c1587345e062b3b3a4e272970d94dfa67c1267f776aca417ce97d540d34d8f3f0723d893a94611d60373b4d7511e8594c5e09c5d5db25c95aab272a9a12916eab15b0a7f19aad94ae4f1e11c479c4ac51a6576c53807d07c5788ed516d1b2fec8094462ce2be5f7dd0bc791fac21a32b7d16c67510584c619b67ae9cc113869c7f67e2a5548f50d3b635576bbf350ae9e038c3dc5dc112b13a4b075aca85824c437b0377fca8b0d8c5c0b1866a96abcbd3e313796fb3a575f05bf06d7caab2fe8a3736fa1f0a8d7c14eb553d255bc9b68d12e3023cd60fc1c07d57a5961149383134f9d96ea869c84ba38ea055f28663bbadeeb6769e2593f1beafdee76aba9a3179ed9a257d1fbd79407596c343b542c10c44275f5802e9425164f07b5473ff663f1dc357771c082e6ef90b1c7cacd09daaa0574ea634887c4a88f60041201da1508b29e73fbda7b73e40b30a50a55df41fa695ff1d560f786f54422e51004929fad6eaf4c52f58c89f9e2147fd6fda082d2c24f5b84be280be6530543ea0fccea752a4d8c34ab8031ec174772f31fb0076fdcb4ae6319ca8baf96ded397e6f8c5c3481da4692bd988a14aa8008dea575d3b03d1cd5aa62ae650e633051af2a3daa9a71d5437141d197060e72e0895b598e372d26aab150e296bcfe2a8555d041a66f0bb79ae5955091507f9de0a195a29df18d11ca5cb7685c776b70ecdfc9ecffc02f5a4904cf7ceee48017b137ecd86f227e1e0e642c2b39d5a118f34294e73895fa9376d5f0c7cba54ba12acd7cf4d905a85e10eadad2b32874dba648afcb27de0de360ad985f86f6bbadd2654af02da9062844efc50a0610c1b2c45cb881570001e31c51fba89847999439c03474aecba2a26fb72cdc269039def15262a6c5f6a5f5cac387786c87522fc768664e15dcc5d762f9fb36c4cbfdb1022c0c57415fd156feca93dcac5a2ada5d263ff7d3726a3d66e39d879aef3f7c4ea37e6bab4f2aaa90f9b7955f26110979862c161e118abd86ad27be829c679cca3ff57b1f9d1b00feaddf3d36a30cefc2e2ffe424cc2e767b537f10a19684882f5af677027e23e0bdf28ec668b14f6921e6b55ce797e5bcd9c2a3db1f755f13807ca42337b4d2f397c63deb133e1961d43138e0ea5a20606c95dc777486ee2d038d041e696f0d2043968071bcb3dc9fadc5705e6075a4791bbfd5d76e2d757e6e2a8ea2eee98152ebe182abb6b1cd68a8487271828a35f5ffd5d522117b4e7abeff214b5e57767d16b4ce4dd2297c8af10fbdd01fcebab1168cd764ac7fd7b6d9afbfb61564ac224822b5549665fc18ae9012a115176016858a196c52946535c8a34f3db245f1300b566e40f5e8e56d7f05bf7014f157fb673642ea7545d81b64da9363cb11408ceea78262bbe19aeae43a7df33f6cecf6d576f827ed0f9da94df6f30f5e69a7562eab37457bb8011f44dfc60ba4d1ec757ecdcaa6ccdfc096b53f535e2f699c03c5bf316529dd43401f181b79a621217401149aa471f0cb9f184f7559ceced8a6d2ba7dfa152b531bc62a3a9af70a25d313eb7debe226c87c3ed46c215562330ef57e14a051de4ea5e5492f973edc9af2b7eed3dbe8edd5c44187d170fe654c191e890f9227438367ac9844e8e0d3ebd1e16d2d99d68f696f62e371949d18bea736bc52b3c7c5f975a189993c9393c4728c3f122b0680eb5de794da071b89ed153db8d25b85d601ecf7228999b3149955899feb1864eca75c6f748b84be9c9e34a930c05efd51c05277af94999c25ae3f4aff9be6bfcd26b16f0a37533e8196972c07a0029faaad39d663203c1f8a3ea3285544ca8ced53abbc44693a7e87b9351d7d24a97d7ced3938e8cb206e430f05e68b55af0d9bd768ab20338384de5e078d11074ddcef47b013578dbe481857dec5f3161bfa089f1ebede9042b16eff2a96716f8a28c18c5dbc1740b66ca1824de97e2c8919caccd8e6ebb91fa0eb626dc84a3e9fe7289f554e5ed8150455d15a4a750cdc4c21838db26b043baa9adb9e4eafebac6cace39b3043a8a14d248c269a49810952883ab3d2be737f253de3e0d13c0627ca62cdcd2c86f93904a2f7866595be840f772387ff191150fb10501ad59f7c411f142fdbe6729ead0e2cc363e8fde7e343036f72c485ad074c5ad968adfb894a4c4260407344ffcfa4acbba468705aeeb81494d68deb370ea93d311fe0a21bb07c2d634377dd07e17d8537df5cf90f3b7449b835e91cb271cd9b791b0c20d95ac8b0279bcc681cccf1f713575f0da2e1cca901e0893b4d1765650b2d415b69dc4ddd68d6d3dba9b32698a7a6bd0759cf4e9aafa0e36f30a0b53bcf706894863468773d1a7624f25ccae9eca8a101d09c35de9cc039dbcad978e3392568c44c6266a6af8f1adfefd1426c98bec76a456e6f281d050d41fd1d6dea440239836fb00845467d71cfe60e88b2d0940ee281b38256615de9da8371792a4035dee6fc8c6be4fa198d7b853e90fc5a5621eddcc49825923eaa444a509078337d90bb84c39e4ba9bdfe456dee17f8fe1bad554826bb18955d1ce6055ef1e15752f7df65670d6526f07ba4d917dbcce37dee756470a5413dcbc4a7a4be6eea6d3dc4bd444391f1edf66fdd7d7f3af96631bc9fb7f010000ffff8c9dcbae64b975447fa551f30248eecd57ff8aa189dc0d43062c4f3c3060f8df8dc59365292378717324a050eaba37f31c723f22565c32c1bed205b4933c6b33ecd91800ea0c9bb2b24fcb3bf7edfd2f105b7e504196246043d777256c1b8eab6399d33e26288b54ff7cc69c06f883c963953d4e11270496517d3c521ea7bdc218571f75a913d0d68a5f071cb193cc6aafd23da80b26c9dcfa68dc8253924c355d0802f69a3eedbcaebedae0a05125cead6d6c349ee16633dc6e69a968cb23e888dbf36129d035cb906dbbe5d64d691c7aa87f616797bd7c8e7f5386102f3a75b8da16bc61531043a12ae64060ed60ecbeb5c3f3978de3f50ab7034fa3507c56e1c56c872b463105da7d53c8eeae9a137b126c3ca566692458aee631b3d97015d830fb124072e64a96ec630695af891a4947b9edefe2ae9806fbbd67375c4383182c1893f738095b577a064fb3fd0ab7ec0c682bb3dbb6b2d0ac5753ab81e1b3f9ec5ab5988f58dee717d07ceee1e6a789457b3a24488c115fbb2c938a4c2d10739372ebf11704855810c28d3b0154affa5e29462b97c5c4206ad680f957533ca738aa13cb5ce9639a1af1ea3ee741e8461bbbf2c23c3ee9ce3179d9cf67280a90d113493fdf7d6b67d83f9ab60bc940d887ba94cb86c48831db32f9516c4a84f7ef7c403649fdaf0ea2cbcd4065acde9ff77be451f69611267f18d5e1f26d8f5c451fba9b47e60bc5b2bb80cf1f06931b4d288fb0d8880d3e545beebef6ec363f2771d5c99f44f2764d6d5d83b9833ec5030aa2aa5caf26f9b6510fa836062ed5f67098bd32ed0dbdc34b38ea87918b36461ad5e8334cd2f2dd77c12f5470ad9e164789d597a9d96f095609857edbb0f70b300202d6a5eccdab9b9c7ceda156358ce0aba793e9a146cb2393687d9b6940ca1ae980ee49c6cefcae9f7e19e2776be3035500a1ecc3b23c6f4f7745fde07293046ae06a3d4da4f8795d457d4912f3dbf2c584df2b7532c74c76dac8fbd4387538567ef5a2f037d626759b151d328e3160ebe2c2b2a9062253bb320b2ae4f750d4f3917307a9c75e93a95eb3e56964aac63a62378f3203e4aac806267eaebbbf1ab9198bf0284c3348c7b46eb432cad604d79608d39a3d6154a65a9be74c73c5960909a218c7f5e2f4ba8a6dd146ab0d0512daced05c8c5e9619f3fca6f81504cd976b922c6a5d0bcc8598933d3fb1b5b509024ba5c15ca145b3582a6ead6d620ea9375eeb23d7692d8658aa4579c55259fd0dabdc382d04a77adc660dd2c88cf1d1f6aecd83bc48cc0ca583edd67ad12786348761fa2f70c0fd02078b598bf3cdd291eb77510f02a08ce6c7f55c2741eef5a77f39fffbbfcf5ffa9fd71ffef8cfbffefb9ffffa5f3f7effedc79ffff1d73ffff8e36f7fffb71fbffe3b3ffef6f73ffefcef1fbffff6ff1ffc3ffda5df7ffb17b58c0d7e696d56fb303b2377ec4c9dde65e7e63745eac93754a412c7a81d8c00cd14edfc3474a57b1622e64747539d343c3bc9a92a55c40f1dbb9b3c8cac6d2b3e1a0972e1435424917645723029d50d24db323c571a8c27eb34efe4c3aed7cd2723d1d05cedc0addbd2470b5ba9a405cfbbddd9cc0ab0f3a84eb7f65254be015fdc6835b364d91ef844d17171466002ee7a09c498175a41f08a28d9a011506f68af60346e1b1e267fe142701058c595a2ec5e6d255856b3829ddcaee9eee85149f05115c3e0e2d61c12b67fce8e2fc3a273302339ee665138d8de840c8aa608c2791634ddf684594cd0d7fba1fcdae22587ae02a8f28ad52db177ef16b7b1e0ec1915a08c886647011d35de302d51d2000cb86fd7b23f44186e2c8d050fdd21ef754f1b952eb8c1c6d9a4262ed6b31c4e88e6f2250957db6c84abd917c38ec894e14cf3a6ee37463f9b1b651b8dd5b5c18d03afaaba0d9afe0f056b69e7cd06c5abe34081eaf4222e9a240fc782904fa2b781ef8287c8e6279bebd24ad7b8294690396e7b087a7443758f0073eaafbc8a73f03a37135d1d0646db160f92bbed6967394a1a4f19da0741a3e210a4e566b58b3311b059d37caf0b1e7b67dd36303bb186ea696b81efc24e4dd6edda5294f1bed47c69ecb26a97d041ab58ee29e903f302106f3b8b6dfba119869391c2f58cfbb1fdebe8a75e6a595600739a0127f778a70d3dfd1e6c3b1b1e25f5876d6b0f8777be3f5d2f2934eb09cf67984d83bed9e2927b60ade42cb31bbd104c8d118aeb5ca3682cd1d914564b64e6e01acdda6cdeb252ddaa8bb1ce775a0bf983722cc7b272ddbb7c0a232fc27029a0b855276549cdc03d0487d28ba7ad435c37a86cc3826dfa994037e0c52c041c91477632f18c3583b17a6a7988f5cd24fd8755dd34cf11129ec3f44e5f651e9e83f0a91e181fcd184660fbd6707d723079d45674efa58452e4f37d698bdefbb408dec2204eadf1adf2f1a9f09bf979dfbac0cdcbc0acce64b56d1bb9ce0eda0e0f4c94266a6963a68de4eb81dfeaf15f238603d0e65ed5e1d7a38051d229da8659ac672a8585d5b828747b98322ff812f502c8da5883787217a845b9c64f009c4221f01f4ef7e32e8fd601226a8abdc7cfa7fdd03a4b0905b42e36c6b687221254a91a0ddcf2f235727035db254e6291899816fb528b100f8c63aa3998d50e93a35e1ec6bf8871b2658d663d79eeecce9f676563d5e84c7c4f2e31cb615ca2819c523f71f4cb16bddcdb13fa28e709ababba7b37881a23204b5643301e866daad49f4bbf4da2930cba44a02fbb0c49d9c5bca200184a75dbdd37f6404dcdce989aabb85c465dcb03eb40cb551d101cf947d67e31ce862c2258929b673d682a34d0a8afb97bbf6062f7321c3653cbb05d3aea0bcc001e4b843a476d8c6b34370bb2883661cba3d6adc6af996d4235b40e374633e07912eba3ff5c1207bfa783198771d42b6155aaa8e2ff1b6a2808c277b75d6e184ca7aa738e8ed632276a222251a101538e6a390ea430191faed075ea0b32e62e0e0f1b6da7f93b19b5133ea8f405a6bcda73ee685313d7fa38ff9efcaa335bd79ff5a8704bb81adec73788535b1bbeb5197dc9d20302d23642014ab7187e7cf5468baaceb08ef355317687181f9fb43c18e1b672a3cac288686d10f394a18e080cc17a67e7e8c3ab5148113b4c9a58d7709e62634a6338ad154d5b46926a2c17a7d6d29b2e504b1fb9cd96055cda8d7c89c25e091e75f7616be54a77eab6e1687de8d306d840ad008552d6b25a07243285cf4d5ca70648047be93ce2b1c11f59a7c3d8c34c49752310738d5ef1b128ea99346e443b7c439d2895ec4d53499328b1a17fc8bf2fa3880834c3c61ad9bdf670486f0c703e2ea899cbb8d4ad8db29729254f78840148720096b7862f3a8a252db16619a6e622787aeb0e06d6f4dcd3e46489a176da023897fed535c650a5e6612b2e6d8683a470b761e167b509e46d92cce154cd5cb9c98db84496aea152491fd53dd4dbb97cb48badc76bd409fa47dbe8938d30543b7848a1d6d7666256d7b7061edc34ab753b9b4f35c9d2f7eb22ecda6d5eff2d1c3dc5c3b82aba24d352b051f6a04aa08d55011b00648c7ed7da09b4f6b89469ca19dc59adda2a2f7b0fdf7f7778b16e2da9ef4ca5e7278879a9bc13968ebabe08bf36bc0ebe8a92c5627b7b76eb9510245e0ce3f490060e416a3f3415fc686377b958eedfb9bf8f88954ad60accebcb14bd2de70b5420178a706ac7c26c2c886b6704993087299489b7525dd44c4ca5163ab350999b093776555d1031f06d79f404059b7b507b1a60f7bed5a9bbbf63185f9fcbdceb423a19ddb0d2a5c7d17bea4b36739b89bb32a531ad26b9add55e48b6cfcd1e66186dad98d9718d68b6ac99f550c2cdc38836d5c5e6d8cd969160097a9d36ff816ead3a69aa3e756a5d2f72cc8e23bc16ec1d16cf25d7b8381c1b38aa2d5ca291a3a0e433e032de1baeb5525f92d835d2fdad2c9d3c22eb528e9e51f7f246706f82238c56154870b4744e66b41a0ed5b33a032298c2db72a12d1a3b3306edb155bf598ea7ba584a7766a44906ce5abd142fa0074e65e987738915ef17318bef58be9d7544ba17f585342ccfdfad7b1893720039af369a21825cbff44ec4aea9bdc9ab541421be4deb7951708f6aed61b653f2caec62cea897e914a9e8aa6d05cd57a58bc906d266fae60a92b6da4f26a1666e8820082fb78dadee57ea2e2bca764b1e99c4bacf38302f13324e0c9fb68b5f6c385c01dd91563b6663a072b26a03897b7349c0aea611e27c679eaf4c436e29eb5488eaabeeed2b93543c6d606a63ecaf9b6f069eeae5b90dc32ac0cd694eecdcef1aa3ebd2f63520d20ff2b6717bcd30ba818549246d9a0edc30ac6a8e366a712f4fe9d38de404ea6e5753b520d6d088000030db78626504112fd20ef04dd5a6ab84d8effc947343326d5e6ee7c7a2a5bd08c6271bf9c7e8317d438d9473eb761193d6b05cb3bb4ea3eed99939091db442473637322242dd9b90a4ac1667d4acef79a8aff78ebc5cb5f3b7f66ebd7a76e1c375223a067b0d70f21de7fd4bae5ea71aa45ac3b7a4654ac08631451bf4dfa66c5144852c6afa37cb5c9e8c9e943f22b3239bc46ea0d9d3629059106940975f77e7c1eab63bdc4778a787c2b8c5714c82702e2cac5e966b94473f98741b2b10da606286c3f4d42e73db67524ed45dd7f54c2c8c8a6986ad696a57ee9356f49b864daeef55c2cb4c97837b964f3f9051474c2080dd262f58b677447ab90d724a7095d540eca8bbcdbcf9cb5986e9dc63f57799f84b100e514f4d6471e1e5b03ad8dd848440875798e7630539e5465fc37463d5d5581828979ab4db7bdaccaf9f81ad6e1a591251bace50d6d8a6de60d7402ea82345b5effb45d66364a257178e163d767a8f350d0956662fcd7b934d8e984547f28e1b31761e42a71dff4007bb0b405b44517ce9d80e1fae441e1bd28b1a647bad81807298647f1301ac5f5a4501da4d29de430f24e2cbc3c2d6491a2e69431f69cf5eb98fd4b9263bdb34e0610cb4620687b656b12405d6485d930199e5a51182a1ff60abd481498f61399bad5204f9f093f892660928936c00dd90237c9ba31b13e532e38a899acc0c0ec7fd21aba4e3539966bc633de6863e0c6f1e091e7df5a65b1f8260cdb480801cc4b709156aad25d49d754d81e7b6372fecfd2dab44e76ab841323752b38ee9015ef1e56dd6eafe861ccdaf82015dd5b1dbad5fbff535cc3a7792bff475a29e8e6913facb68fb706455700a8d0fedb88544cc89b4cf1efdb5d2b2bbaf9f224a09132f73d0cc6a21ea9722a3d26f15133fdb7af93926a6a9780a1b129be37be3fda258b22b549d7a99737996030adfe6810131d292cecb64381de1b88f98d5b208834c1d4bb262c14faaa6b92c722ebbb666f0eea9ab63138961fbedbdb2fa7f01d94d31cccf81a72bd5998ddd7464c8296bed211f652c8fbbc8c18a58078330669aa97273ba2d1b10dd7c4f377aed624736131362f8aec5ed6afbc06274c7aac2a8479c0dc5d7c8b23d9851d82c059ca12184ea9181caf9aa1ac5a79e4601e551b149f84bd707bbafddc263694804d48afe3efbf9e2d04a6495ceb9c628506c898ed03e144c4864b94e435b9299b17d558821523f088a2b33efcfd87cee61bbb7621bb1a0feb5e305638aa9e1f8a1c2a3b287e781de4d30651d95b84ee0820bdfe4bc474e6df9af0d4da8122f6bc204a8ced17c67f7be2a9695b57858f665488ca2a7af725903f454d1416ba7e7b2344c62ee74ea634a9d976d3b960ada08d8882612fa1c09d8cef411eb48fdbe53bcbe8467e4f7590c25ae631dc6b0b6b6a53519887a304583e26246bcb6f634a17545a768cd11a6c9e991632d570fd3762db0f1a6208fbacc3d8a32d3d83633675818284c30c75db7ca96ca089925b32dc388df1bbcc84e40a5bc23ab5e66df7510f2a42739b0ad281eac5d31e1ea995472aad3737498aeddba3b3c74ba79da406d35a32476f56046328eb236b3b42386b32f376837567ea79f7964394beff988dadd3673b653f25b914b1b66a664ccdabd02a99de44ebd70d842af768946eb367d21047357cd321e2d22d57e97eefab82f4508c8eb96ac7ba07bc319186dad5974fe912397c51bc751f0eb83c5279b1f284531e9f4a10697a06ae8c5430ac278c90518d65b43f372e54ec325a3216ad5570c492abaee95637783a17cb11381e0c3b6467f8415696b3b22b6d4345a217f550baecac36f5595fbb105db1edf65b2bb6314b39ddd049c6faee263a0c8b5fca7ad53b331c1ee4de38b1426582605b0d9eca3155a6155cc5edb8fcee3a51f3af3c429562cba0b676037be4763cb9a069f92b9d84b3c523543e5feca109fd02c989e6a6179bae7ce6958b2a3a256ac5a3be950f9819de7be651ec770a9ba26a8bcd58a1d6e2e857e6389b3689272c628e1f3cddb84876603d58209e43679881e633cd65c0ecf421cd58b2d648f0877b87706d9425804a4ba595e772043658780cd66cb4f3a9f6c1e38c65f4ea3e0dc0d5a75afba5d07439150c60a1d1d4c8cb1da0cdf44208750517c12a6d6b1478ebc01e49b3ef3e421a874d93ae1d7727aa7119d7ac341a8bb4804fb3e3243b26260c49b35178fb6ada720f5be2bb29f450a4425bbb78765b9970ecac29c2e7020a6ca4edba88829d5ed4310b01633b0849a256a55006b6bfa687d3735b8918add9705f5c5a21e18e65d27bdc2e2b3902d6c8b61d978d44c66157b3425f26727df67bb03662cfd1282b1b4ad84399c46f834e3220946e13ad3b0dd44b16fe7db90c1e0afd3f5177359c80381c9a263ed68fbac8bad4182471d7ac58921ef35011f2e50a4aaf456a8414db47302cfa7b256722e3ef34fe44144b12c9fdd42b1b01091b9d21e98187bb6e6898079f463fa6bdd9cc3844676e7ebd69d2d0d014b1ed0d2643494886dc537cbf12f876d1033cc150a643e745670b6006d6b0e2387676e2b34b87dbbb637eb845be816afe7ae9636cbb07f5b9f9e48fed5e135d68c355a7e50b05688374d373189d8b95ec2a381c0e8d429fb7639cbe5c7423119e67968e8a0f44daaa5cf36956f094182d4057dbaf659fbe89b54d9a55e1246aae9ff62f466e43c121bd65a96bbbcfad85af3da04e15a6efe23dd41c9fae550147dddc90ac0df255c067a9a24ce529de9d30e7afb9fa32c375651839a813cea1846914ca8ae4af7e0b6531c0b9b06cf2b180be18bfe57db61472b1de3a25a65a44bfcaa7c820c90ac8680803e74cb58a1132dcbff3e791117392e2c107d3d2b32216352ed5e3d92a51d3ac6fa6c0603f4371db9db11e4e8cfdbe336ebdd9084ebf8e057ab344ea6201257ccf3765d0e49d6bfa12741815addaa9d5b849c8cfc04a712b4b3f1991cf864a8ab5805f476966a634bfc88f51b85efcfafe49764a7646dc3d3106ef289c600d8a6b715efc4d636756709072fcfce0e589f659091d670449bdb10ea735ca83818abde19648f8e9ea5aca7dfce6ea92c57d720c17b4589b01cbebd0c7d3a00ce8f657c4cd8812bbb738fb7a334f172ad34a90889d4d50a5af0f422ad2a442a5868747f8f587b0a039c26da7c0041b6f1603de303fbbc5975195d73e0b23084c1046e6fd9bd7deeb6ccba6ddfe22bd22efad419860dc09f6b319b47c1a2251a0631682785479cb268ae34d5e1c9ca689628cc08c158a079509c9e32b0877a6a4f69348b19732e8b149ac2daf52938bd879e06a4bccd654b1b16b7c3fd428a467851d847f7640fc48cae169a7b9b68398905d4f4338888cd740caea9f9b5d7f6d27fe263ba2c7222b55c3806e469963e581c917a637b81f6fc77d7993ce977c372d5463c6002b725bb6107ef0e04417b31c2d71ef2923cc38b34192fd1785549100d1050d57954854c67bc61c3b07dbd86a574232164197f16e79709bf5584f82a05e05fe809bad0b1467c3046bebb4e49a8eba37b8c730519f07652b4fae455c9bb5781eaeb2f466751145f25e1d2af8f6b41b7d4adcdaa3bf4f0a050efc5e849fd12b771736032013338f48904357d1b9646cf081dbbebf17f7d932a463097249cecd26a009c55978dc08947b411fec5b25ce0df651859b41fbaa7ee0b38356c8097fb8822be1daafeca3a337ac2c0d5e93909047b2c6b6207bb3c5127532d988faca2f09f9757ae2af6fa6ed02336b178ba7587b3625f2d5a5c4b2aa2b2602399df5830bfda26512b0c4397d325986e385719c3244d633a50be755865b6da2863b7a1c1ced7bb9609d1f6e6c36ac6af185b381781b7e8b8126ca31bad32b6a10f4e8a9c2e1e3525f5e7d5e2f8a85f4ac630650f0a8f291e01b831cd0253084f1a9a9d4a8f332e5a489ed8611a4b7eb2aa49b17dd69eb6a7ae262220c22bb445c8d6b7510ce79c9c30fa9be6ded90c1e8abf5d993ed025bd2f7e72a5d4a78ae199e5c3074d3cb9606be97bcc083646d8828768360b2b788dc3b4233aeb5b556d33e0dafa16e2e777c134caf1deeca7c586eca8d211a4fcd90a67ae9c664e8e42daa8413d63f674e919b6c91d1f8c1a8ddef3ecebc7d8b35f125b57b1b90b53e8e6c117584086cf83ea1a6b390575f63d3d9870ed583a67b9c1366b9b0b878f544ca034cc56072065161338ee33e3b6b13582433968cff5a1ef49cd31fbb04ea16c926f97ce13eaaa16f6b37322f3d06774ba97a75265fb267d8306b0a539b9ec0a8b0ce84cea78c84e6fa84f474f5ebff149230d2c8d74d0cf963557fc8bdfb8cf80781d10804905278b4d9372e5d853fd980deabfa9be2659179a914a66539aab66ee112e486f7bdb6c8e70bfbe9c257ab341d6132ced655ed4a84dc324b39fe99a29686329b3e80bfa2d88083517d78e67d02479574c1f8d4ad834f4c4b677dbf3b785e6b2da5bb2f980f5b42263d04cb8dc1bdacdb209835f67161ee2923c70989bd34479ad2c3532c1b40d9dce35c041dd03c257a93ad6391170a6c8c588e6cacac1379eaebecb5a4dbd0b405e614e7545e9c51cfcc0577d9e8b35d9926fe6b210bf6c5187871e7247a62f087925ed2b18a77e93cb7fb5598d7a57eb8469e5917707c8a77b20d610a23022d2ac868eca81b7377d6bbeb84bb517fffa393615cba36e0d52624c014944cdfe64f07ab7831234544d638597bd7bb45db0b4d76d01b604e3f2a3d02aedfb81f0cf2ba6f595bbd56068e93d7683309e684e1bf456deafe54c0e2502be464eabeae8138a554ef5661deea74d752e0df575f04ad27a841df564b3faa94eeeb525c91af1edc18d9197648a0302290d6f978843532bcb0d655bef6ca25d54e4146d2c9310c6627cbdfc9f0263338d6e53aafed9ad6b6b891f52dfce46819ff655ddd974d86dc77607e7e145bcff6303c8aa064aefb534f59689581bbbf60f0c3a90f6a37e6fff7ce634cde3e068a5dec356cfa98520dd21297d4633780b661532075588d16709cb3a45fc6e6e8c30389d230f9e0b6103f791d2b51f15a65d6944f49a89a09e8032ab01facc66a898eb9802aacdb0bd0842b2ade0242734bce280ebb473804fa0284fb1cc04b1672e04fa02dd6b209be94ab2becfffb964d242d6ea3cb7b83f1c73d8222c3290885871c44ef63b89efd30205fe2d3593338049f5d0211b5e73aa5fa0d7ad72adbe1133a7959ddd599f64e3f666e97f2057e5052514a6d80a9fecb7ea0fcc559e8aad09d59cce2781071a73bb45a93aaeea2bba0d72f79e171d061668e38259bff7aae3a0222eddc826460a13da13cc6d25e7206d4fb505bb6f9fc542ac1ef35b3de5eb73991ef18b88ad293076ef93ed68f2d4f321da27b3572c23352873e959388d32eded38b9cda2e644b86371617403467a646e4f11635e964ef6a7ca39c78404fbfe5f60b8f97ec8fefc4aac7f0569979eb3cff8b42983c8acf5d255eade473d560a35f4ae3d94b107925f39c7d78c868a8c63aa248bbccdfd3cdbff070000ffff8c9dcbaa65cb75447f45dcb60fe463ce7ce8578c3bf2bd18192c77dc3018ffbb895cab8c768c3caa6a098aab53a7f65e2b733e2246fcddfd6d21485fb7fde83b326d4ac4f251eec84f31d05b00c973e3915a5d897e883be83b99b322594381efb72a048d49a45b4c2417996ea1a28371bd73384cbc8bf6062fe30b65f2affb02ca17908133127da792a26007b817be99362474f2cb5abbdc4f7efd330fd75e0c24d695e0bfdd8dfc53953513351c10f046cbe7d8041448a884728de5d22dc4a8eeaa283b922e570fdbe9e4b3eeb2bad4135d9e43400b158fb25eb9b02592a197e9ae9073fd908f2f7815e5fda33546e620cf4e715de66391cbcfa4e46741718343e54e2139bdca01cafe9108140e55d56fe93afd855b8f149b27e6a02ec49c33bfe1993196dd01d88712ea79817681152efbbee8dc9bcc428864901dc21b5cf595cbd3ba769fab02438c3f7d9ac8dce15f9643b7beae19092f6f4e723aafea2356cd8521fcdc359985a671460b68a3bad49a3ecc9465371d07d8e46543ecadb8b43065ca11996b30ba375a1458ff9b0cd1dbea94983de037ed22d334bc1db113ce56d139482fd3fa0be6c3bb0a483d4fc28ed20f41ce75543f66fef18f86048fdcb54d6794de25cbb78ff5caadbe89d96baae78717efc27ebc9527526806acab2272d2d07acfe5d268a1f79fb82e7fc8926b75151da7ffaf7f493634f48bc77a091c6d6bfa87f9fa708aa480c4df0b61f296c45786eafc3290b52238f26045de0a02eaca5e439a35afb34fa8acdfae73ed8a2f5b071ede78657ecee9122ede192d73edea55fa1da73be48cf37abe4c8936fcbe56d9483ab6423b60e1be19e6016a3afc9490e7ca6106d780a926113b3156ca88fdb4d77d7d4b1328f22639d18f10957722074c5e8b5d51c77c632fbc3a097515917d7299d55e5c0b5f3fe283c38dca7dab9aad18ee9b41f145f12e17929cf86238008f62b402556976946799131d3094a60dbc47f689a6510622d9eb93f10d4cdbeee9d1b0df111cef7a2645a47454e4dcfb7edd4d12cf7f5c3560003cb9b6dc75810e283982db6f571fc53e888b54b946cc0d304e891d095a63d79931c9520dfea2fa0c21faa8c21305204db2902b9713bacf3a12fa2d0d0b5d172d3be32729ea79e71465e167ff0dbc74431bd672fcf5a4a8dc09eb17d68efcf69e5ed6579cb8a15f405b6ac2e60bb93a73af62076c28d9c9b3ac3295b8c1c161d208a848d005f5f814b41effccd46f357fa54e8e5c82d3f80ff0a4d28775d4864f3bae27e63db0458127e9c4baa689116eedb1174221aeeabfba9b66a47881335d21f156280911cec4d1deb74e0537462850a1fe34bce9ad0e8538849662cd85181c37673e0b1ccd2f2e5ee7f2192ff4f494a9cadd6b86ba73ce857495aed518dc67f2c842b234951beff437cf98fbba6ab11fb38412d630515700162d888bea5910659eeb654b8de14c2c1be83f7fd3517db81eb19f2c1c582b1dc2fd4d14d16b30aacda7c1a8a89f97580f11c612e533f4e7fbe5ded964eec337f282e0ec227d947839c7498f7f9743a175a8e73ca9112f1096164a25af2f4d510c3225666d65b8b75132c5e9eb9a6f9af039673a61d915b0eff712f2bd868bb0952e3cc1e2a8634f3764f575e1c6d7dd3f0fed371a528c95fa2b0fd792e664f84592323f23e4a2cde923e6b9a380b8217038a18422836c1f4eb9c5e07bb66c6ed9ff20abed67e206a290b84c9891d7ec91dc5aca9bcaa765ad72a1637d8abe5fd353c672c88bfaf4d5516b2b637d79015c96f6257ef2ead4f38f46ad6d828da745c32c13e2e46c959ed75bdc428d7e446f7e9d9e5dfdcf60474ff6ec4e661c2df16edd6fa96e8716c425eb9a3bb2af4d779fbbe788f7cffee5fcefff3effc9ffbc7ff8db7ffee5dffff8d7fffaedcf7ffaed8ffff8cb1fbffffed7bffddb6f3f7eca6f7ffddbef7ffcf76f7ffed3ffff657ff71ffdf94fff6c3d73b6e9ad515d3a893ce3e8c959b24f50b067bacf778cb239352813613321d7ad5f3dca2e1f54732930c72f342dbfabcfc3d55a3610e0f5db2643ed1504586895d1f67d0e66db0c29d23137d873ece1d7aafa4064b7eaaa71e1773b6ba8826aade963241665f9eefab8c4e746b511632177701f98053c942a4d5c1c132a4b17c598e5189531be1ccbef440170495d2fa7bc9c8e68e82b15ab8b5f6c4fd4d13d427a4e78f5e41d73c6b18eff06f1a9c6fdc049ea4550401bb661f9f9803cbe8a03a3c2c839301d163742c882849825d4c1a312daad418530a3e23b539cecea7812a5dc04b851831d804dfa7ef0bc5edb881ee9da9f3667784e413944fa32c16f935f03d349a9b25cd1d47ad31c10d2f343f7b6c7565ebf64f0db1c5e62962843f0673f521ad8fd6297e11b68daeb0f8605af53c85075b913c18ae76ef5de6b0cb5e915f57c444f107f95c8ebdb5b49c63b67ae657b90b3ca48458a3bfe74729d94c75308b6ebee758130348598b039a4922ee6820e5c5197d40ef71e1c984e45a3f60235bfbce697b377cad80d5b646bd02bce3ee932545443f73ea9ae06cd7a3b1110a839351580d64f38b10abe85a232e015555ac884137768d38bcb6a47f431833d06bd1a43c1dd60d6e7d6aecbf7017a0b2120d3afdb7ce62861fab4c1fb4934007569b6cfdff40dee2a31bc0c9411212603433588bd9056331b98a8ad654acd0ae44f895579461e380dfe744a1175892f978302fe6b8d17514e2a5801ff1045ba43b7da535b14bae5145e0219b776c96b33595d18d7e53bb03879cb2e967aa8ca18616b8787fcd63e0ef9d6ff36119f1908be938e8fecd2ed7ffe691cd6281f13f5b78e1d53e8852b52cf766805691b6bc2d8a5612813320439ec99fc7a54a5d93aac671e0cb4e3b2b4e2f4ef517b10673249d583deaac649c6f5ecbdb1dd36a086df8561a7480569b3d614d99d1ab24e2cea1ad1bb9b0624ca71e88f2eb5860a5511d88b496b07136975418acb357d9ed9f7059091895469ed501b93e694f7b9f1c2282869107f75ec14ae9c6da3a3515831b2236946e7ce461fddb4b3f2cfba1c6a32000fc76f38009cadc7cb0a749fb2a961066307a36db0e722694ab6039249057b031225781652d084154f132469dfd87c1159e7982d310e53d848cf051e951a22e4d1ed98ee9dd716ad0d12831bd614ada6ce00103af273a6fe4622efc2380be9584bc7413a55af3608a0b748af70fa97ace02b8b47b8baef27971c89f6c6c9f80bd46b9927c50b18deb603d7b2e8df59f11074690b61cf10086f57f73f4a7b0125e88c3e10cd521cf8a9058a7a4afb5e6a45fcf391dad9d532242529c09e4c2de0bc0815431f5bbda693cd55550a3e980389284a1d027a46476b27a46c8d442ca396be7b33c542626c5860e5ab656cdf12a8062756cc82f697fdd5ab2bd201c9ae7acaa985b9d3943917aefc2e9fb7bf5d07490ec55ab6dd60a155f7eaf382d0f91ac42b0b3deb8cc529ff16116e6728bd3c3267e7da8154a9455798ba9ed860870da958a220faaf4c2ea3b6f2e4bd1a5791e4cf779147b3220a4438457be515bc56962380c4df19ed22ccba803eb5dce462a08d86dfaaa9f5228aba8572ad51f2a2777a4ec959c38f7e758493ee40ddb32061343d05ee0f2cb1e751797afd523b2c0b6223f4c5a1608c01e05c6d077a47bd8498228860ac6339457fa8de581761c220e75204e34633777cae4f7fc447f5899c18955668b4b46190dbd791c2188d3e4ed73d4620e34010a2dd09fb9a91a35ef64c2e6118199fe5ca7b76f4eed270bd79b9210397ae78835f2c97e7805949b509a6a84b284064af5f6b238dcdab33faf4de2f57e9a9c91a8e3610bfa73748b0455770e3c601ab15671dd77dde7e846d472f58516c69e9b1b017ea2b48e39566c0677f2bf4cc4271ae953b6d9abb14d2b843c6602758aee88e6bebba0a3db7314ecf63d2f8d88bf900f71efc3cc7d5099ab21e353f23eac380268f712f67f848269ac8c395831948b2255a290c6b5dc2cfc46658ae2a7fd674a724c4a3e360225d5ed176013397ad9cb2c3670090dc66785c80c0828561cd451844bac79a5a56dbbc29220283cbf75f8f874ad5f58263413631b41253102b5fc6281c6180a93df5122135b6224c433d4b360a156b858e45f49b11aeaf91ba85e4d0deca6730fd23d31bb9f8adae9ab3569f87d679f684957e64f8a4fa0938807c4f2ec481e16f13f60c28e2c3629ccce65497c421521dd5e387cb14c2c29feda57ed4a105537f9bdf462babd6622e4b6c098746ab83e596d2c5eb00e252572a13190f0b1213ba7e7e2ec679b5f4a43ebd1d9eb1ff84b17334c68b1e7884cb86d5bc71c6576b5f0b16c7ba963c14d8c30a4a83a3a766f3281e09938254bbb287240d5e4b75f5d15e8eea5fdc598ce94d77e4aaa01831a0bd8d7671202cdd2905259306b1782715e3c5f0769157fcd5a9ca94c54ead6ab8ec5f7b2a60db8f5f3d1f94cc89a5d8dc8523284d507eacbfca2991a904bdc5a1494c3019a455e90b1d906ecfddd073ce459a8b589b6cce6fa747554e1344d94bd9cb8588f93520292e4b053544c98aafdf76890b32b3903fa5f1660ddae61470836de7682b11f72b0eca2719f4b53375158998a39f58122fb4530f227253ca512241c2a1293856804d812cde824c112406b310c57882be2837079b5c6cbd70a196c5f5c3f7a64d482b79678c60b064b58271a465ba2656f97bad42589c699122dfde244288e5845e15f5e48b37db1326b45a555271abb2d640509bbc3f5ca60ae30111ead4c2cec762a303ba26631c7da83de51187a0acc380a7501ee8aaa32a9dd90527821a35df0e7fb782d31a75037952d3494597c2a775a9c140279fb1bb8b6d85cd8b060a95a239203c147f0da0518d4beb060eb1eb41692e055859a011ac6a3e9bc9f17cd7f7d4fd1167b9e30250fbbfff08d7c81cc006efa4f24d3bc95906c77553cf3aae0f457c9b3a494ef8553a52d72406d8ae769dabfb9d966a55217551f55a7d67a34d7ce626fd722c77d99cd1260174fb00e05c45dfe72d94f1702e89d090dfd697d67100a24c0ad6f1b4c139a842a6fa5796ab84f323b4e37371be54e8d577665a7fbaf0c4eff46717a92fd0dde58a7d81216988280d066d7737d6fdb16aa7086c5c7a0ab307ec7f5eb8641ae6118e7f76feb096404bf11cf781416b54dd83fe7f9f1a22d9612d375a7701565f0c8894b3a1158090a02e79b56a096ac40919aece9f97f214f21e9dd41b263f81c3915ad91554e2cb5dedea770c9aa077710b4a3d59a510ab4539c9bd508a695c0513b3da31e7fe899dba57a0d86e41b2dc98559b047b257c96f95d41264bde720f8d406d971834c592cd46a769d918a0f81ee6059ae8f372d1e61c00b0dd97b1ab8a57c49e2b6a63fcf8d470d3f7db256252d1709b80f549a4ee3b2bc4e0798f72790432798bcbf255316cd12dd4f1b2e5420bb667afe08a7af1fe2c34ea2a1b2e8e959b9195e20f35381b86021b6023efe3641bfb56656ced63f3a7531991d19b8310542f15c20d4a6a2c971ca18d791286b0f4649ca6a07a1339838a1087ee6a94511b429f5311e400e029a6d0758692af07932c3572755f775bd149df95ac26f1dd68a901839a64a4cd170a3d043cf5e1b0286db5316a40b0a28952a29e698fbd0659d7766bf0937ad7807692610387c6cc16a8fc957be874a9ab1657401b8dacf0c1281707dfa2f4a6c8e713c319790053761e7f12f701efbafa6e28860f6120022efb4fa5f0f89105a9d4deae411d832c75ed8a441943f8499965438672b64dab025f211b01068467050e63aaee1337b90809e3536e752ac8705873a40f231efa7d99f90b03a12a0141c3e39d73edc158d52d792936d897d3af4b1057c006aae2c740b0b9cf1edf975db92ecdd6ec312ed9f2d711a9feb2ac0d8dfc16ef0d01bfeac4a0a612980e09941a6702ebd6757c6cccd505e128132584285d0da2932d3b01669cb3164750887b591bf625f310d030daefab27fdc43a4141053b640dcc4ebfd3c344cf890247ecc2960097b46c99cc5516ede97858ed82081d633e5d9ea41e4ccd69f165ae26b920e6ac2793f4b2a41a1a0138bd5b463f565f19c983f464bc374e0f4f658c0cd8ebae6fca3082cb4b57df1c8119d93816639f6b8a15b12e477f08a9c6498e6ed1f12bc217194cb3ace248d579e1888b033357637f5a67f731c7bd8ad05711bb72e8a9cec775a11a8970a413521b6217b7e48504785ddac41cb0436656e1147c55be45590c98ecb615022d3406e5fbe912a6c77d5a2f9ce8dac7f43be1286fe0d1d4d417a65859fdc5744185150bae3925371109ad4a64789b006fd5e326dc99808cb42a812fd25ef4c130bf78af067d71933eb35d826f77ba33995395770a2600cec6e95df0af3d40e4eeee80bba046caf84f51d353fb66f524a7729036155bd538d6c7e1c82e611f806c5d039a91b3bd4756a4c4ac05fe0619c9472073bd1fb916f01fb3028023b67a89be7ea6877d742f432660c750cdad0a9810ee3202885a1713bebbb43650d8e73c081517f474608eebf12b63927dfe09a8e6243d4fe836a56ec06a67ea5e0eb84cd644eed2d07e0d2b46a58785a7c028850667d1c1d278af228169fa435497c821fe65f5dcbdb97d34148980105031544741d666cc1c5e0729adadd083a6ad2537665d0126de044a4a8215e9546f8a4641ba9d02cca364523edfac5bbf434357d257d9f48e54c15f98691c9f99a78f96b115ce3d962a416f356e620c5592dc60c94c199f010acff3d6692356ce704b8ce984c5f6cd69d334233c09275a66b965aa2c35411d4bb0b6362c8a190335bd36054980c4491344eda34a0297d5b516e1b6e5bad7ba17bec7f3a10aa5b260543c856f6bdc20f81ac934a9713738dc21cf3323c2bdd33d4425abe91fad50ea7d317173c8695f49b15848b7d0bbb37681bcbb459f033cd9fbb85e3a60847c4892425f7f283fbd802753666398df30c6ce8f0c9740de7dd37e706264b8b43b2d3ecb16597901b756bdf1aebaf23ded208be2ad7d1530fbc8c46623747824f7ede5a40dc3ea52828c90acbbfaa3b86b52dc50335d8514117d16a6732bddc24b31a93e6c83df75985deeb4bab53bc63823524a556c9e2fdd6d6db2ba80399e6b55dc8b14b93c43f3551b21b3c2ef4320a2a927e6885262241cde57c9551ee4f780ecf02232d5155c4dcaa6e0408c3cd50b6ee07b34715d1bbea29b2da61e71043eada1a060e829dbe822c302e7228a10981555eb1f6c2fb5e7b4c96f88aabb065eef21b253cd9fdd988f67d8f7bf5541dce0e43084fab852b9aa564a8c3b9b7b11bf36a001591ad7016adbeb1e50c3025ff08cea3678982a5a7540dec25d09a7902df7b4b2bf228ba8292f7371bfac2abc4bfab0a78d0ab53b807e3c1ff6701d3aa7c6cfb9af9196ffd8367bc156a2e8bf9b642faaee63e5ecec817705df12f97f7174ec1e0dad14d4527c2d2d95e57605296d34efadd86b6136d265e8a99800f0482ec694a7b10a992a5cf42ebf0c95435a64fa817813b22811b0a326af6ab98727d5bfda382f8973793e076c3177f3c5d7d5d179af095e1ff4009822347899b3014d342bac70372483e05cbec0298780e41983d2b312bca77857a8c462370a6464e226495c2276f8ebbac47a1d8ced5586de7a94c8b2384073a8ee2f7ced5ecf90df331ce4ea1f6a173bd6127d5430dd5485b962f8c4845da4dfdabd432e2bf1c4be64560ea515774c44ce4be23ff7d289d633e9bf003757fa53cbeff2eb47fc615f0d5fa720786047b706ffdbd647833de9d4b0cbd546bad9475cc8bd194c1c3a53565705b5a27faecf2d8c9bf2f52d38a59ef3c47738530a4787886a9de92ef036f2120e25317762aca7642177f8cbbf592a2f85db4f68eaba510aea48df6e79bf4c8eaace382c38cf590663a41ce739164275666b3e8c684a8945bbbaf380f8c175b86053ead16c62f274dd15290b43dc0dbc3192e7c18eaf29076288d4adc2e3af4c978e8be23a6ee4a0e8790bf6f6ddc455b04463ccf343950c548042154d0b03d33ec6de5e4b35b54f744e77218083887e15121e6e5c8ed37eb1c4947a173b88b3be1179c3f74ad114db03207e2e4688692010de1e15893ae957974e6cef3d7e6217feba8228de7224a55f4397ae6103e73820073d0a44f13f7ef6a93d3d223185121fd1dbac96cb87b4f508322a3ec34b55a95a7521c6f30a2fe12ee8fb2f4c7c351d829751dac42e472c9c4a5ca6224f06ceab6c6345a0c313ee0b9cbe90dc1d999bcf65983f93997f7d8bcf38aa39bc4155ae7fd7c08815089fab66ca8b69c867fc697b9f03fb2aeb1766055a4f9a0febebea747de38b763aa22ea5e0c08a478d32d35425c5447f31a4dafd25a17e9e81a22f52245afdf82f5b59eab95c1a252b8d3ff17a07c3e7889a7180cd1a0a015dce73394b0d7c51da3bd79fc2945ec6ef0ee74eeb97776d6b93dfc5fba29a5d440d58565546b8e85c6756624a7e5fb59c537d502db555ea7ad72919eb826e56ee4a04260c7521dedd55a9de1a4ed3d1e35367f3e24304afe20c6ef7e2ac8fada56ea31c57c35cea77e6f9d85c35568f7ee5f3348e5ac906edd12608902da307b618ec869f958d587a36870c115c06a7126b6c0fd5c91085dc873a928e8663e4628f58c55ab12e3d3a42b8846a198ea66ff20225bad6ab07e63aeda987e1e5926e881f1fbdd5145c930a05a9b52b64d50a7283d72c8e891db1c2daf079add8a4ab1e243043b2f5aa826a5d78fb348f86b7fdfe46e930adc042eeb941ac6e4b8541a3e922a73343effcaf1bc845fed5490dd492a5d346436255f6f0af21b4e6f3ca5a0e27adb1b1a2aabd346f44d488c1847b214215998a31179261a82352425f7785b341b75123ef3a5a40f4d034a465ad70f9fdaf1b36814cd72a80c4c7fc94423c40b0d2dde023071f6668539f141bf16b83bf55ab7b23dc46df9dfaba2df86ef5883b9d1c0e74526cdc42d39e72a345a7923e723193f9da5aa884505286f3a7aee80876aecfd35106b07b250f03c48d3f11b36ce4171c7c91831b34caa336f786c16b3a64e0ce06fee93b9dcc031e6432b3947c1cb484e666bef21f8b4ad37b91df66eb70e936b9195cf5a13e74cc5f720389244be4f595047b12336b476a8fc6fafed6eaadef973d94c11eefb494773330305a93e7be5c88cdc7bcbb2b04b3d29dc46539b131842953e5b06bb475e5c36fa3773749efd15303ec9fca9331682e3429c00f55cf195700edd49bc3d4e4211aa82f6e782ff23ede4dbe1e9af805eb6d5922e741a1d06b973d0abc056d403156f027e4eb3bcf945a0ae6056bf155c0716e471f5061f1d4d9eaeeb43a165a7699f37a0069a7b9cef019ab521820610a7de09865b525058eb70b7a9d2aaaf8eb42af1cdd02b4db4b9c024ecee6ea2d3a88cb51d6b6d64c2ec7b2b006efa261a34cebbbb6cd416f9530dd4e85ae0a0755a58e5b15f78e04d07bc2587031887caa58353b8157b30cad795c9421d46b8f0a22571e1d258d7d6d16e282460e223e353c1b3f53fd3cbf58577eb4f398f5fa410e51554a63a943c7df6bcbc98988faec43cd536083626ee277e1576080d186bc8c4e35b518176cd44505f5475ad1471e6f78adc034742fd3fb0659ce0e71cd0aab5d988237152bcdc58e7a3784a835d5f1195085b5510326cf48f127fd9adb3de168bc327ea476913acf879d42777dea58de883c3d39be6aaf7b38db9553af2b45f7659b56a233ce65e27fd18d55aa2501d057a9969091c0c05dbf431368f8dac1425714d72d5c2f283b6776bf720e279a21991e94f0aeb4861c99a662dcab32adede8ab36d6bb737aac95c0a84078c3a4f28d17f30e3892bc396075d299d3c0332894cf840a9fd27e06177c90dca3810198b9e7dc7803c7ad39f6afe9cd0eee9be93223232ef9dd927a78490c33fb73dcc4a69ddec4275722e8fba94405f449cf1ee78529181882d61c16fc6d6d55953a11e3a7889e37ea7cedf4a646bb7d0f7aae33056ad8fff85b79c528191507c28e5a5c122525c7675ac1e31955234bebc042fb75edc96259a3f7de6392fb42da8665c23b6953fca27b07a483f4a3a2eda9fc43501674e55427f498dbf1eb4a797835133b8841bfed4345f88c8504fa7b277efd1015f33e3d10f83e166c6d75908b74a2bb5fb249b5ef51b237b04e1dc764b7ffe9ff000000ffff8c9ddb6a24471044dff515c3bc375455d655ffb218190d8bf16ab578c7c660f6df4d64b7c113916d4b2f82618466fa529d95197182805fca12d489c999f036a48dab4de598e34c3897f94395956b635436fc7155d664ac829de3260a5248d40d88b80bb1328f043386d4211d4d58a5644cb7c8f12c01983d7aebe8ce7ee234ac319912104e5ccc218dfc659d89c37a6478c7d97992612f2cfc8fbaf7a244bd01fc0f9f174fce591f61cd604d4fd2aec7d58fbe828ee987ccd821354c019144489f31e7f2e897c37d1e088fe180efff8de6d9cea14f50ec24ee53c2d05244ce9ce7449293e0d282e153c144a609f1193d1b7677a1372eb825884d56511715e4913a6e454a6ed78fdbebea540f18041345ba46f0b44cb594d781eec690c0f5361e8fd83e6e45e35fa17ea3d7d679998ef89410c3c2f0fa21af779ccf64ad963c043fe4ad7c29c120d050471a9a6d597303c7cc9555325164d1c8656549d50d8dc742d3d9cee88efab0dc4220aa9fdfb19248bb178cae93274501127ec0d117a05f179c8af2bc6fe8502735dc5897b6b91310552aa47ef5c31081d6a41422c87265e1e5984b0c0a9c9810737176e75ba9b22018ec1f52dca28e2ab4bfcd4e05128f8636a70f543a64d8fa706aebd19c7a3a362d754e49c89633bedf0856b4439fdd822c1a663c87966c0fc19696f0c5506914c14733c44393519ce862737f0b2bea30067d600acb4316e1171fa11763c87501e25e968730b662a4b38a59e48a0ade573864473254768d51391e39c30dc7c834245ef068d8bcef42cad58c0480aa030b6e17ec9ed4090f309bf9a86db2434111b6ad00b2da342a20cd56a5644c8b983a87f77c590902f8808b90563b7e782f0a65155ae574fd0ff178cab43916c01f4b13ba9b0ae66ed9960e4280eb16de183b8077dc616a4b94f968f9b0ccbba1132b1d48f44c124bc0529f6626c933b18bcda5083a14e641dffed442a4939c7024945b005136095489532bc1c28477858b412cae6ca82c584a85251f122add8da83d1a435ca2c85d1a4e8ecc6c84c7bb9d42bb459cb9b74bebec4ba47ce1410815677094cecaa9727349f80c907f68778889b709f4da2381261f818a6d7d926825b4f913b30be31512279c3d6cd8fb586ab2c46198d5b20489f9948de756050a16d629665f6665ec8dca993f42c37343602aee3836b120a10b631c27824192e0add9c01aa6d5ed07b6776ebf66c4ecb08acd5047eb0c1cee2d1107629f2599e2b1380e76227662b869239b6c3c42f00c7c8c99f79adc96dd1d58bd0eee00f8b8ca183dd81e7b55c79eb27733c5c18d1ee0271696022e40c1f9e31a0747358b6a22c47e21a8670957b5a589ed1c433d2a80211ccdd8674aca678fc21be2ac27d9396ea1ebfe605a0441f11eac28cb800f6818a08b79a1c4ae02bb209de9f0cb86406b9433cba452c6de576cc361685cf855d36ca5680fa1797ebba6289f0018a3ed731c97907a2d734d4dbfa9ad65298133f6c434971762f1b18f010f47711b6b31502d90b1ed924af0ff8538b25a173765727d9d7cd8a849aaf7ed613d85c84316e348a93ff3845498871cbd1a6f8f80d1d23b1c0ed1c2ca38309625d311732be349a1072e73b55321479119516ab52ba9aef8234dc8c705f7120bd3bc52185deae3bcb280aad06435398ba88eb338ab4101c11e89179f8624e78f98e3d052335a920b80d6d58493552025fe1f0bfa81015b9d536d5383b1bbc98101a8b5f35c40fc3c31ace9acae7275dde3dd7c3aae4915363abef1d174ca28173e16fa185a3f514098298e658f022c5372cc17d805bcb32a489966b03168a14d9ddc8181e8c49ecc56f07df25d8706dbb402f50a4f3dddc5c17b2584cb156d763ec23d0e174b5f3508518f5a77ae3542dda7e0b5c52f1aae3c4dee832446eab369722d39f178c9e0bc43df2614d988509ef051158be1c8b62212b502765c17339099e8dfcd0590ac878110c592ac294196c7807f42f8c44066974084d5006917cb1790602d1f2f7ef2df3f9e2e974f78dff5edfdf5f6e5fa7cb9de6f7fdeb7dbdbcfb7d7d75fbe7ede5e5e5fb694caf647b9fafb7efffef2f9767dbefce57f7ffdf6dbfbdbb7fb4ff7f75f6f5fbf5f9f2fffd456d7fbfbfde5cbbf5f7fc27ffbf1f437000000ffff0300443c0a67ce080200" + ], + "rawHeaders": { + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-Request-ID", + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "913d1245eefa08b0-LAX", + "connection": "close", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Tue, 18 Feb 2025 09:43:11 GMT", + "openai-model": "text-embedding-ada-002-v2", + "openai-organization": "anabasis-llc", + "openai-processing-ms": "176", + "openai-version": "2020-10-01", + "server": "cloudflare", + "set-cookie": "_cfuvid=fsqvJ8TVn4GZpiKWo76QXVTVtp3LGyRtRbOHMCiqLpU-1739871791937-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "transfer-encoding": "chunked", + "via": "envoy-router-749cdcf4bd-fl8q4", + "x-content-type-options": "nosniff", + "x-envoy-upstream-service-time": "65", + "x-ratelimit-limit-requests": "10000", + "x-ratelimit-limit-tokens": "5000000", + "x-ratelimit-remaining-requests": "9999", + "x-ratelimit-remaining-tokens": "4999946", + "x-ratelimit-reset-requests": "6ms", + "x-ratelimit-reset-tokens": "0s", + "x-request-id": "req_4eb0a9c14604e1f1a60df674a8a9b902" + }, + "responseIsBinary": false + } +] diff --git a/src/services/code-indexer/__fixtures__/search.json b/src/services/code-indexer/__fixtures__/search.json new file mode 100644 index 00000000000..0a2fd8aa388 --- /dev/null +++ b/src/services/code-indexer/__fixtures__/search.json @@ -0,0 +1,94 @@ +[ + { + "scope": "https://api.openai.com:443", + "method": "POST", + "path": "/v1/embeddings", + "body": { + "model": "text-embedding-ada-002", + "input": [ + "class TestClass {\n\tconstructor() {}\n\n\ttestMethod() {\n\t\treturn true\n\t}\n}", + "constructor() {}", + "testMethod() {\n\t\treturn true\n\t}", + "function testFunction() {\n\treturn \"test\"\n}", + "() => {\n\tconsole.log(\"arrow\")\n}" + ] + }, + "status": 200, + "response": [ + "1f8b08000000000000038c5bcbae24d771dceb2b065c4b4246be93bfe2956412060d5b5e980b03fa7923abc7163bb2a9192eb8a8a95b7dea9c7c444446fdfd0f5fbefcf05f7ffdf79ffff5d71f7efcf2c37ffcf2dfbffef0c7bdf6d35f7efdcb0f3f7ef9973f7cf9f2e5cbdf9fffbfddf9f37ffef5e79f7efae56ffff6dcfefce32f7ffbe9e7fff9e1c72ff2ff57fe71d3ff3d69ff933f8b7a6795f61f7f7bd1208111ffc7c53fc99f456a6242eafdaaba6688e9fb5534cc34fdedb1a89e72adb78b125668efa4074013aa1db4866ef118ba5726d37909e2e513ef0b484bf77ebf688294497ed9c15486beaf7550709ff78b5aede879df421d9d54a5cd823a5c21ef2bd00e08c46805928ad0a13d1c490bbcaf0b9d256feb7a7e2c250d5674ab84678077d0daecfdbd100ac46f57f5dc283eda1c03fb52ddefb76a8e5547f00ea49bcffba2ac67501c01526e39f453566d5d7caf1954c568adda9e51b42b3aead674aeaa6ea5c327508244bc1f2c1c216a145856935eb481d996e060159dd238c1dad32d2782351aa54909d32a31fefeb262289b3a610cb8e4346f82d598fdf6cdf6aaab18fc3dde1405016fecbc9dc093da5298e123b4ac8e04af49e031a0c850ed1ec3fb7355ba3da7f9b92e33d9b4035e2ace51289998a493c59875c6297066750f6cac21ad1c4685f0133108ebac7336660938dfdc155d46570d0d349db92ac687ca86403adadecf4b24dde5ad9cbe4e3135a81448eaa82605ad201c420fdda3d927bf5d75b371095a54a68f899cb40f0fe5a8d70c747070a23c43f818543b0da05ccc6d08cdf98132d09f9ba63a286b43e05367519222a0bd72d404c57c840dbfff2dc5cf496f87e4f337ebc69c0667d3a747eede79737a3dc71a4dcd40bdc3f3749ea7c83ae8b0b6460e45abfbf6334aa3524dfa636c2f13e79c9f192b2e2df74c9fe32bcdb7f2f6ecbf8827f2d4fd5037a5c74a8acf7b04bca2adb226a9f36d061a059079f628b76ed7d2d34d45438d1b9f89143703f18d55de55cbe80a3b755890547221533a7400265ee138f5069679ce40c561a2f458d70809ce014f6f7081f7ad61205058ee5174a7636a0823ec1ba183b73aca3b1939c816c7f0f66400396ad6d6fc621ad48fc5b3caede2c72c1bda592ff5146aa7828914826fe8108152c4eb73b407144aa73697c19e569b0394c2e6adcf3fb9e589318eb78fc921e91034755f49efa620b62849a52c9e8464f119d81e98d30e2cfa0a576e659b435b4a089486aa3487b1968a2a573213716e0f6211a5f82e12b19834a93c23b24418154a2946cf8f7dac455a5a1d42ef851a34830771b50e1c60be2098365c3a222e8b98922c0a988256689c30d4b4cb2272c1f1c1b0db4ff25b655b9742e81c24e1ef28e9f9638b821e50dafb8443cc9604c092189786a63004f79668621b96d24640d1c418e429b2a4a9b20972b6ebd08e4ad4ec1b30ce846a58f06ba5a6d68122400fb8916c293e1c2e35329c8eafa6926934c4b79a3b31eef029ee6eb0e9748ad42dc4852106882d2d4525b7a217aa73fef8789de3abec402b7507749415758736d7a6d2965e952753d2c5d278ff7c1665d20a2cd28a22d06ae38a4ad036f1382ca5c7e220f787171a4309a4d5226f7e82b6380389180f05a3b9320d4a21a88705b8b466cacc30cf68e4c029da351e3cfd7eaea63135bc2cedb0b4a3c4002ac15910cbd8af968349b137ecfbd4b01ce57521c614e7cdcc6d744cb990cca2472ec4e8463567479b4e51c110750f14032df15aac36cc605cb72531ae564dfe31d3d9a8a3a7869484bc678d4d24e741680c8a61a6b45b1d02874a6fef93cb8af6d138efb5d0b0b84f0307eada6e40517c202214710e6763d908e9c05a269af7607a3a1900b6aa0fe919061b250eb4882647594f9172b7f3d0d90461122585ba3c3a33d585bba958cc4a88473d82e1f010dd1a7582799bbf9cda5ddb16293ad38341a9496835ad0ab505e5086a1fc0cf57b0ce6ce112e0157df84cb1356731199d7fc371f491702f1606266d8f8bd55b9b94434b3eb648ed840f6334e9862607abdb7639263bd6c5b049abd4087a3aa283e99b8e87791c0e6dab67ea3f071dcf8a66022c8f0188f4833b3f8a599b5397d79efd7f36d5cb3393518f3a38517215046365e004c06761e509ddee704f4a54b53063843113277d5d349613706113b41ffe5ca5cc8842c65ce7546b4d38556099cdb23e99be75c939821f7561ca0e7bf07cd726fef451687db5a2803b78c16992703d0d6aa1fe87ecc86912732ed67842cec5870ade6a5ec59a97b5460b8377d7ac1996bc4215de14b3beaa70711e8a544fcb70eb558c692783ad89956e822bc4e212dac62c78835bc96c819623f7569b318a455b707c45451cbe389aabb3b146b9277ed0478d6631afd295fc4f31bb62f12bc701d899b9fc8e981033952413ad9cb4ed9775708f2270ae405f914634603b51237a2666c2e7b5f98c3741e6f7cabc42ab8aa5ba926ec5dc5dec281cd6c54ad38b626957b01eb21a386bc5e5e3d6679c26337640912eae9b7373652dc7ad7377d9162c66d3818521144c0d397221505b58781513e88c130a884c637029e1e6d2f621c8cc71703a723c8e66d719075beca0e6bde73e59dde132e05520a5874f5e4ba29bb9ea6c48b3ca0cf3d538199fe59906dc79e5ab2e42eaa8aeb612829318a8562efcf3ba67c65ba8955367b2079d7670e06145534f963267c9e6c1dd9b91c86fc0e6571c6ce67051095b11864351a572fa4c1eb4e0fc58c95ced93419e9a8e708f2cc05dfda0a99c33ac92d9491331228d1d3550655dc1a53d187949ae5ec49cd91ab354f43bfad36a5e3a1753460afcc0dcee129eb53c118ffb6eb5cd48ca0ea8ce70123976f02c1e5c45556f690dacca70007458700ddb81f00643dec2540b7719d8f40acb9cb9b58a39133e78b4264fd543b2ecf6cdbc94157bec577dab81d19be98e78399c77729c7ef63b6d879c7c3621b323610614326ec7ef51e19624275884699f0a2a3b64cd649d62e13957714db526ad473ab304c7ec50605dd0d21eacc6ef35be66072e342eed75f8f9c28f3e02eca1f82f73483adb405611451e596b7658cff5a07d07ad04f672a11e03ada50109af7f46247f87c28a8c4b0fed949ac7b60f5a7b28cc7892dcf03964bbb23c5993acf6101c6a18cfeaa94d2534d953f0497cfd2c4a7f9c8f6bee81b029c4c427f9e7630ace6c3d4a95ed045289eee0d6eb63a676384c9ba9b0662511d9567364e14839447a1b5d156be580b8f12c5097739b7f4fc55264547165c0ceccd87e71e76d8832398a558d6ca3e7545b80377538df926e722398b53067ddc91c4ea2c44e6c714c255d1667421eba02037322351b3908aea240af25837558b07e9d16c2a2c1c7f47775e546b8a4d9b68651730a99e2e12ab6d471f99b4de1fa56fd7daa8744ba304e73ec70f75a5776f05edc5d4f23fd3a80ca296e4de6703b1c76d3fd4c7d03a176872d8e9d4d710f582ba21e85c1d7b97094f589da5a1227b7480af8aa72b4f17844aa6dcf8776612073884eac3788c7786b30cb33dfac1c19f08c5891aca0c3521387c36e1f60cd60073e62872a4a61cd6c0ceb4a60e62cbeea26d31990ae972f784cbca6a4ddf46fcce33e8394a76a95991e3d7699a11c35a4aa0fe6b6b102bf18b4cdfc8cb4b314c67042425a4c8ba787f6a8bfb486d62c9733a40dfbae6a86d5f0b36ee1c9d083ad23f59a8b3e0ec94455a30ea2c22aa335ec4b5dc6ad87031f57e8abfa218fb8b95a77d7b064180f7ca14a1d5ef636957b854c64e4f13aae98b1894663cdd02a634efa01d62d7352bf9ae3916e1e0cb0afc562c68eae9c99cfc70680a5167e86c5155a309ecb2aa6192f01664bf48e6c8bb549512e8dbca9c6cfbe4232fb9b71f4da6def3aa9b4687df2283fb19314bf57cdd8f191a926ec0d301d3086c40399489398acd13bcad8fa4063b70e95230f5f12f41a0b875ef7e68e8f830d08e2d5dd2c5fe2510f3800f7461b3da2a8d8996fd4d8f4a5e4cb7c71aaac06d4d9b2b30a4c1c3f147cb5b306339ea588ac6f6bcdf561ec62add8e929eea8b3deb5abb4c7f15daf1ffd681051d56c04961cf735df1fe7b6baf8215e9ffca692eb2d6530a2ddea71863dbe1def180736e79245174cc5872ac7b2d757dbef9a84f278fb760a7b3a4b630d5857f2d0e5848c54ab6b8eecb2c31196106c45723b03e68f5a2a4aa655ce60237690c97234d65acfeac658bb5d994a43e1474073e4ac319a4b68ea10dffc7aed7bc686f6a0c28b723e006b6c5e82ec941f21bce4aae44d5ddfb273947bc863263e3e6d719da08db91fb8bc8afd72a003340d2dc92bd0f5693b836893ec10e6163590376fc90b44f7d8dc3eda854ce4778cd9303b24a309c69a14910cc39fd9679cc3d191758c1c20afe3c94d679d9b29732c91f9383dbf27c657cd5eeecc83dd7cd4d8f77ba3cde458638f97e535bfdf2f3aeadbd3563cb5823f93119faddb077a4c4c6932c75e01ed9a9bbd9a3d3a8be9bcb98aeaa4254f4c2c1bd9fc06b63676ab93e6d38af3558fe79227368b4ae024de3a96591439cee01708c7ba5a19c1c6046b4ad2b03436cb9a6e6d3fdff4b40e1bbdb650f67e02c5b3f90fee1ecc6a55c65f9b4daf8be34c03d50fda0f5f7f000bbb95ed46a6d42d7b3d07bbc2c1463b8d8acea3baaf39fb5e5df3e28791489445f01e6ee57eff7ae7152f12d967fe628fede3485bb17325664733b66c9d95c559333947515b317d77933afe74b4038723af917ffc7c4f11f5a6c0bcca43556e8453d2f6a6c7f9724034f9a9363b5962ae10513d6c9b59c9f1dab0372e86336e3c26d9171cd6fc35c50227b5e449261a3ae75b2dc863b6fbe6667dfdae69c0dfe93c2527deec822fc0a075bef97b0280d5cd25004c0b3e8f7ed68b559d7194d0daef044e0bc44865820dde1391d7f9ff49d6b0f2692622226befec3ecdf998a95f9f2e9a0f2593ae933bef2ca00ad2d73ebf5fe7cc991feb7ec1304c279e5a8bf3618364ad91e66d8fff170000ffff8c9dcd8e654952845fa555fb6a85ff45b8cfab203643b7d020316c582021de1d59dc5b23d2ec24996c904a35d995f79e13e16e6ef639e23db81b39aeda68e1550f4bdb9c5ac2059a8ba7070ef131d8bf0aa5ae4da6813929125945f54931fb9ec99d4bdf9449fc1c7e7aa81c794f862064b2e2e9739d0aac93f947e1ea25e37716772e3284bfe7d720012893a15d5152147a9ab94e3c30b4e39e6cc11b8089e21746d7d7e3088b015f39be97b47f8ec081e481b6442356a3a1aca8af26cdefa0598d88d8be51cae8d0b1fbe8c3511927d8488d92709bbc50b30e3f040bd9842336b1f2866387a603091d69b344e7d28861b2b25aeafa80e5aabe140cdee1b1445c92c44baeb0deef38d2cc320cb54088cfd8778f0f912d4f3391625b3f08b8788959714ef9c8e0a336ceabef04eed6c9366fb5671b6a0d3972d34f9bd808ada1707f6518bd3f166e060d60a3aa6b97bac8da8dbd63c8498c68c69e705fe8043a1655f1094b5e731f621b15091ba9738573fad87210eb10bbe1e0f96423f46ccc06f998802186ada8ab7b23f5c582c39591c5928fef814fc4275f919d934b5fdd9ece266938f3940ca09e7ea6df8aae352b8334a3dc780895c305c90aecf6c336ccf086ff44de48e4f3c4417855aa43fd552cbc8f12c9f559262e02c44415443075462bd33ce99cdd44091c628d823b4c6e909e9a60e3fbdebe8b4f2fd8ae4d9a9ea949713963bebe6ad6b75a6787c9fab08ca349d5773702d158e46d4c5f5884d0daf26decdb5b25fef2d96572d0e01573f1faceced972b622c5812f9e6e87425a802ffece1323ee3b69b55ea604b45592368574249356787ae561b4d51cf3b3b2f066c379476ef5a3dde78b33d7188de5920358d2a26f4b008a74ffeaf078f7957110ce64131086483294dd3b24e09cd63b9d87c28dde5abbd5397b4bbe6375b3f5ec5e7168f04546404486a131d08144c915f3ed3b23d3d0c49cff09fb61da8c44bd8c45f63572b365246d97c935e66e7cac4d22b7525280c2162cc109dbc7398c5cd973a4b85f955eebb046156878fd3bee88d867d6167da47376f0780f5301699d76610871742660b9791c89746ccb60a3ae4543055bb8d4986482948eb01ad88df3ee8dd3e5ac5bc89448eaefa08c96b14618f21362b0e272f3cdeb389847b11292c1e9f18097a936770123391dcc8a4204731c9c5b1f823561d39c34fa24d37dcf4e971fed1808b0714d86592ff317828b2de77a2d9dbcc16412e4a9cab4464dc2ade376f4b5f280e37590eac9cfb9cd321dcb0fe1143794e1ea7ab07dd2a91dfbc493d2072d95da35316ed1fbd862f1c83e666091df5f5d909f2573d73565a9c48e4e571e070bbc23611aeba48becfdef9d4684517eb992fbbc17505a6ab98415510ace33b5e6aba3e2d5e8acb3c59cb677fa4916a877869c1e1b78a72d71c0c93dd3220643bc393cf039dd32c785ee8c39a19c2ab68bc7ebb9078ab04ce207772cab1b2a3c6f802e7462b56d57d6d781c285b2e5c8c4ac3dc487ff0c505930832e09235ca849f38de967d28efa82ee6b2ad255a1d092bc62cef9e8f27d39d272edc35e9b33674c6476362ebe7ee1464b69dfa9da9f8b668b6b7ce7f92bda628a3eb917a632fe0d8099dfe25a3e81555dcec49971463fd4bee97be1147022fda5f3991b0f65d646012ce19aba17343d5bdbddf98d71bfb6030d3c36d85922c4ee4d3a46ae33c3a5fa278cadf0136c2a72f3da473ed31cbc08df9155c3f218239b7cba7670a00c15fde1afbfd0371ef1f2b11de6570a493e00b3ec181e0af9aee0ccbb1c64ef6b354f1c85e38407727647aae7cea5febca757c376a35be773e3f190c53bef6283c8f6960ec011fc9427f624fe0dac7847eca3215bbfd01a8693d8e98f46c7d72b3b7bb774bf40c8953488f798b4a1322d0b2809a6c674958bc9a40bf027b90021feb307e1c833bfb785e677bad11809dd86275bf74d82007df4e09d127615025f4b2275e10772877fc18f7bf30bf2a3a3f3fd1366b706757170767165b0e1b8956050bbe58cb400ed629e7c2415b80355c6cefd3adb84d012e9ee526e3c856c9e3d3ad0c1f1cdee2ffc799fc4921eff05ef00b39f9365ccaf21f5e03d23491393b5fb786fe6275569277bba33689818d9850905073633e1ed9574a8edad3a529f3d1cad7fba940c23bb23410f74b7a6b79219980ff2c0c4472ae94b4101a14f8ad986ec2795ef27b3728007f9b47e8825bc008328a67850fc64e8c1d5b0774a3135912d538a88836026f720e6ab8745af6776df8289b594f286527d73f78c59fd04db26c033db5bcac7c7131f8c2e640b4576ea958a2bdc91ce4d84721ddfd329774ebe8105156cb77f1ce240b3da9cf34355ddacc2089fe73949f3429e4eb678291cc131ae52ede469315b3125f8f5eb8f9950bfc01ed6cc6bddef960fca482f3925208e2d1fe59ce076e332d1af4f516b1728a22e7de7c443c3706a7c0b06f1a9a8bdc31fc12f19a478c51a17c2424c191043f5af4c0f986c7c043e8d1dcda26b8f464ace516a621abe6071eb5889d907396b6b26cd426f7153b120b28ad1adf78d2cdb3cecf601bd53d40287e0e0027c829fb6e42c7dc146d961d5205d72adb0475ba14f9892b106d85d2184c199383a6e887570ad48b570c7692cff24ca889653684a7949d697dbc013fe6a19b726f8ba5b4c201be43d9ee8593fa8e462e97a377ab127b7cc8d1a579b9081c0ed158e4fcd47c1f01d7931578727d4f716e66c2ccdd4016c20440dc32f210731f45db5035df7acf8f2801ce5ff7941ac4b01b5ede86086259a6a0130eeba034cfeac002e2e7546922ffae7fafdeaee8b2e92005785e4cab8a180e1fa1efcb6cd89dc073b31d2e98b4f35b4f4aed53c388dd91c5f4e845ab67f875569e7f461313ea7f7562c5ff70e468c3c0685a3d6564c44201fa6b8fcc61b7ed4ba4518ad4787c2eb253a87ed9e4fccb2dbf8e583020399934fdab8533cae660cd45f093c795d7539d58decf3a0d63e8aa266302b8abcd109070a9f9435e5cc99fe446b9d3cce5a868e125f239109ae3dccc0afe668b7c4077f3eae0278ae07deb3f98a239c29645082ff591b139c60e2c653bcd6c1a412bc3b58f8c13c8427bfd2b3ec6457a6649841658758bba21d4f0d896185e7bebf72d07ffaa15cf573b16b24772fb1d3f8eaad75f9ead8b50f1f2633dd6c75dc09ff3dab798c8e7b6357cf717131c40462a4426e988f1fc1eb9ec68684fd35e70ff4c508b9d1132a2f438217c6ee0fcc271999fe3f91a79b43e06ff612d46850a52c98f78024aaa5fb8446d82574fa6bf556632744d5fe06c9dd566e209ad8940e43b0bc89e5b0b3f0e99d9045f35bffdac25120ee9b822ed0f42e854fb458ba75dcf64ae7ae9392ef7814eff282399ae566e01ae4499c6d2186428bc92af1c9cc463358df7ac6574c8f588af1cfb2ade5f3c23c46d79b9cdddc5e38367638b799cf443eacc4d9ed3a6a3ae01689c763471b0b018e7e6a18811f7772c965728fb39102a245a3d4667ccd99c3f257217b21b3df752338ec85cb6b3361c337f234dcbe030d64b2e22487731f9065e5bbb5c1997e140909402e57170fe9730c03a6e9b7af852f4b7e259f8f9de04be3c22121e7eff86976f1ef13993a178d03cbc7c73f7d5588fc5054e484e628e0e265a63e9ce0fcd2423da990153fbcb7e77da28ed9f0dceaf6bd2da2c440d8f806a77481b5bbd486e2c7bbc44d9e77f6220a50e00afd9a5efffee308331eef1ebc187c8b0a25fde76320f855c915044cb9001e7093601a2d89caafd7e05d7c9a75b9d874269eb3b3d853ca8de9cfa765472fa7d7c04ec4ef3659795e31f99e5173cb5ee27f53f1e3be87c75a0119fc1ebf89269d92f27926dd05cafc151a0426b8effafd4a2a2df74ca1c266a79e49c514b9ad044ccaedc88b72324b309b5e0113b5d04ac1d9147f165aaa9034d371ee321324b9d42ccfc8459620b0f27d8efd179cab961ee27db90506025c14d5c3daa7851d517614480a231a6f48aa58a3171ec8d652dce35d0fb5b83dae65bb8305c02cf9b942ad210dc26d7b567203f8a4165aaf09a908242ff9d2b20c171fabb6d167cbe2a574b88da44bac5332ee7d44b3e1e39d19d998f674b4200de0ee121f41bb6cb2266935d6527dc35a0e02fcc788fd1ba2607b8f0204b1e986af9fe70215e67c93e893250209fc38051e3e5e9a368086c9c3d47598e6fdb8e101beb712e5f111b36c80c4b4e0e5e08663c6cbf3ca0ed80c77ba8cc1e1166f99e176afc53f566248af814fcf161a99985b1f6943af233e36a2007cf33da0e4208f1de199f71c635d02709223ba30a699c1a5dec2d92105d413a04bc1489fa571d522f2d2656a6400ef71696e32d3f15e1c40baeb11ce088ce611950fe3cf11ef394c92c6db730ca3d39112ec919719bd9b5934cfac7f03f666697abd4ea6647d203ca658a50a349839f29b6d6f91d9f4387a59a51ab7cb61171805a17e3e4667dfd66d0c13390f5b3bcbe4ad47a37a842f5ced9c51c5569f3ad27ebac5122fe0c600989d328ff0ea27103bd2df63ca607a00ca3e5ee27e2e22d5e5d9a83952893bb67af0011371a007cabaa53d4b226a8ec940b0df09af92f15e1adbdd8805d25f452ef47cd1f6bcea880d77b3f8ae318e9222700df67829fb2ca136c8b692b3a658528d4bb92e41f7c3022b8c98a7d5426e7722cb1b634e1f937f2dfe3202db548a734bfa8caf7e6902b36529a35f9349ea913a1392c300d18c3f8386cb5bcb90c4164ab1c53e49b508dccbfbb5e238ac0cdc0e559e53e29c84319f3da151bd95de018e80e9ba47db7d54180d78f0d9b11085813ddd0c882d3bbbfc2cea3275a5855d5b36e2c2fde7241784770d37cb4f948de7083a281726231a109f95ee0cc51ec509dfd6a81cf994c073219e2003398df7f3c4cd012b38c140199578b1adc385af1db88adcbf9349c2f836b5a8df2bf08c0bdb09b0590de2b781174cd7159a35894f82d672c42327bbb2de0abd472ebeca33431826202c1e199639e287a19989496c78e1c9e2d9212d4cef192d1b1495f40e61215e21323fcae454272842219a902308d99376f41e6af966ebbbe579080aad1be194b66fef8a253b058813f666b023d724c4003ce0326881d35239564fdbd170294cab940e7623efdcc06d21d4c36371d4b50ddfb56892eb6e42938b8975da77a0c6630b1de4bacb2cb5e112f516b126dd05f0b83e0977e0614ca4aedf7c73400022213b4edcad306c01bdfd22235b264e8abc7f47e43cb4f6ad4bf3e076d39703cbe840a3e6ab863099ef4afb2e93e3b19239e207ca48b6b4c57e028f0b35e7470e3aaacca5a0d3691df0fa122561c3bdd12b46820da032b2644ae3a3dff4119bf2b8dae835e6b710b2ea0371efd5ca9f159c1c871a7134c12550a35f43cf05ce0615280f187dcb9cd57c65620c3a264a29f3ba5eae98d3c20f8555470e13f53dff7c5cf6f9de7367b2ebf1713c0a097a73916960c38cf42ad80120b0e58abdf7d299e71d04f2d2ddca705d038b2d40ecd9c3bc73cbea975f6e89f71ffef3fdfffff3fa3bfffdfec31ffff1d77ffbf35ffef3c75f7efbf1e7bffff5cf3ffef8dbdffff5c7af1ff3e36f7fffe3cffffaf197dffef1bbfd9fbff497dffe89a6d468e378f273755ca986bcb35dbac32bc9b04ae115d8edc3bfef6eacd492304ce9e020e10f1651067c86b3e6610fc92cee45ebf4e28b6d611bb06e8fde187b325b4afe53efcea442631b7b0388cf11c500498ae3b6c0cc018ec22ef1d9028e875a272b8931065dbc4df41e07120ebc287419ca6fcc873f6828f67be007e4fcf9936d2890808433870eb5459e447f3afce54ef8d97287558d7f5c62f60f7db2c42875d3aac2b8712c7d946f2232212689830fec576133876f1905428fd53ed7d274993c6c27d532c1c7766ceea436a0535c20e033f4380ab087834c3675dd8d5422acccd2055a386e53d0cee89ef3e3b2aff78bda23329b63991febb4707e60658f0e13bc654a7c4b9a07b7bc061a117a2889180e24b2e699db86df5f2ad8eaccb5b4b887ea5f341089ddb8a31fe0a42193c3c43697a3bfc4feb8d8f4f503b0aa5e7c9fb099ca6a1ec07283bf5fc7da9225150996cc669a722639c0b90ae372c62e18e205330c92c2be1ef9c4319fa874810360cb8169d0c3a4043da76571b01b8dcf7ecd5e435130d0fb2d2493b2533722640f8bdde86366d4570fbd44dcfa4883335eab4e3ff025f6ae929c1fe0fc9525bf411a86e8f473f1d0eea32bc180f44e5170218ecb030e418f3714dc8b60b6ecf0b1b57d9900fde6a30deba5dd419e90c8566279b054cc89a918ab2b73a577f638c258ad77c38694231af2c3e369368016e8da5ce0a80572d3685cd6c322252511ac5b9d7e94055fb020dbc22b8175b0390600e89fb39883aae60c4367c08c654b8ae11612772f9a523f21595c28bb0c6e403ab684f17029c64704f280af5f560738a647bc610910b814e11f6315b0e0491ee94230465a843abc9ecddba797c8c05e760e47f5066d25c754c66f80852dc6486889053571e5483e75b59f16caeace9391ae26393e96dd0069a42f21d22e4244e6ce47bac41e4cb37929f3004faf201a3b12d4812267990aa2892d5c26bfebcb749d50843370733570ebf1b0cf660b570466cd72a686e2f33b12de8172b6f84d427e3d78ca0f55d8643aeb8ef6aba4d3c5a209f9c26162e329fd057b720cab616493c41536339bd8a3815ae6d4ed86095656881cbf7c162621e6f0511085c78d2f10c4f0f43f74766ee1eea63310342d4b45988d50bb2e2b39d83ccf44943b0719ee780cb5a740a537029cbcddcff07fe1d2bbcea49c90d8c0e5ecef5e03679928bae82d7821e309e024057e0766a478e63c26590d9543f37d9f642a9e233bb3a5178d283574147a1b491b0149a8ea5c2c594f8fd34a505f7921a12300065f25ebad9f1be4b87304e33936a84312023e59027247b831932bc633ce4468871b57bd2f89a8d4c75f0005502dd9b4ecbe2647b2fcd80720fdf93ec223d6bf7a4dfb0bdbea744506d62949538718ba60d51ebac2ca0a19a4dd1c287f7c73f771721b7fe353c219825ca88858d82b74b3fdb871c5ef276652f98dc837e8f22c4c7b44ca4612a044cc8019c38557877f83e0ea30de6383327e406dd305d2e4d07851fc5c460740bc2270c5c18babf7f1b4096112a6c781caa94f40cf5a748b613039c3f92750ddb3e408c966700352a9ca6d00285de2867eb9495c46c0431832922f743c0cef142b17a6f432d6f34b8a976156c9e236f36ad5ca01dc46a290b960fb38f3447d6fb762bbcb49fcf62c4a3fb49558a7397a55356ca687aded805030bb15a72ffb95b0474d38fd88d69e25fe3a2c195fec2a3d8d4392cdafe86b65dcb3770afcf658f747b0f8fb758250cff6b68053476960397acd1c1326af616fe9d6f12d76342b60602723eaef891825485eb0a465c59ee7e974d74c5fad94b20cc83a2f17552cc2962be6c060896429f3625a9958c7e2c0eb48d91280b1c1bf8a37bac43113d6af6f503f244d873e40468438ab64455284f7913820d63ab0c504fb9578c01886555d6c8ac5a42647dc568d85ee4abbc3d7e829c41047c6e4b0ea73ef5c6abbe6eef096af36968213776e7cbb5c865127f09687e038e77a23b0c256b577d099a9606aec0930f540ddd0b1bc1f2b65c313bead68979db3f8226489eeddd6cdef32dc7c2cf8c68090288bf0e073e5c60d629231d2fca688e505bdd17b2521a0a314dbfe4d2062cb8db0b007641fbe4d506109caa0f08385e5eca35ab60d8241e249c455f640ab8458507604a915c85aeb4a66cc3fd52f84562318ae79c938b295e5692a6040c4cbe680dd992a020cf8bb6cf0da8e6dec0a63a8b565c91bb632f34614ebbd9829e148ba188755022e3d715da3df177ce5b153abbf560c7ed931b6f3ac21eeaa7a8e469e8bf6e1d317346ab19dc18f2ee9ebc4a2ec072838dcdc5255612b9700ef06f12089db47979815a10e9da32358ba47dfc01130b55d973dac09b1afec9a124ae2e58cc9b293cbd2ab9279cb1e17a71e4e9b2c5d57101b06404604e28ae56739c0d7e74c1ee66e23e52d580ad6e2a16fd06ab8c5c2ff5e5c40d82f9dbca9ef13b51133dc25d8e88807ba11f4945e7c8ce6f6285dca07bc69cbdedfd8f86cc5ea811cb988657b8e20cf905e4ad9938c63df379fe44ff39a0b0d685df4b7cb64e4e4309fb1a502f4aa8fdb1a5e0eb1d35b7d6e201e314e1bb7a614da1760c20f81df440dcbdb08daf1b829500e994c5a714c22c8cd1578b66af735f611cbfebe1b0095d0f5c208b448e87c3d608cd11346f36b0333172f05bd08e02518e333c7e4f8091480bc580788f0113b4263252c3b60ae3b5696e9f9ec9d8a368eaed8dcc1da5c9beec3aa9a682ae41319a8104b0522e7d2886066589288716ca5e68df5db7905105a1628dc627d7ec8a2a12213e305e209002ab2bc6c4744cb0b4096127c9d761926e05e008687d72f3c0d8ad563f1524170764abcf4c00725e7ce4091974ae22e1cb32add4d89bc917c0dc0b2b1d1029df36ca15862782019a401ea4cdee87dbc0483899974699abfd28570675808a88baab0962642d75fb7297705791dd2890120eee06010426047705c37d4227b77317ce9d2d5538ec07d0a2626927191102f8da773f80f958a0a80c272b0e75415278eee129f50f498fcaaeb00b4e6a918cda8a49323b0978ca523cc5f8cd78360dc77461680c327630fdb3231c9d4bb19dbac72a4f20161e5f09e16ec3e485d741c7074d8e4179e8157913427f913b331d841453e0cb10082e63d5bf2e596989814b314a286ad50f65a2825ed2b6002217a3f680afc4594d79114742dc4de43d01c50018eeaedd07cd8da036dc4947885137838f870b3b87208a3538510fb85cdeb85790014a785156b694a8b474f29f13fbc629b3f876884ece37cadf161dfa099ac4d5a175560ba579667bdef3d38213ef6ded032741c8375000270c5f832f22b03cddbfb3a11251e64c7d05d1c8eab91d3d22525fb88a7d59798b8b11f52825b7059b42b1aa3d1c58b23365732efe169008ce6e6249b98ae97426eb10d0b363b4af6419525072804ad2dcb24c16f2d999481d2582957bf85b40628fe5a9045cf4738da440ee1237aa6d9cc670d16c1b1232dcbb3086eb7dee5783e2c9bae1204b682f5e6f7d9afe2a0daba955886cc2eac5b5cef9863a967082bb18ed090f092c9fef78260a011a3844d9633d1b0784a1cf5bca2ba7c8aef2a09d44272a878c8bf4924010b7042f69d2e00de4fc80372a2fb890ac33e243066938b68c0c6cf1118fdd3d40edb676c1fdd7c82174f089a0f5a14140031d8a3b61d5dec15c82bf2636f173644d26cdd0809fb066864f93acd56d692e8c439b33567d158a1d152bb40e17f70bf60a2227c79d88a99f339d59e2e2ed0bd5b98de5a10da45c72c51870270271e9ae5119634c6518095b33315d4c5d0773696a85e58623bbc4bf5207c282596c389c60987a92789e17a0978440ceb626d25f1f766162264213b2e38d0e2bd1eef9849ea7e61c832ab1fa03c31122affc4206c3353624c3d95ba731e4a67cfa885e8721ba43a00aa4ce4f01b04ddc1cfd7da584c9b428d0327571a1c19adbec39c7524b08e350e5bfac7e74080f703f30db04acbfda502f9327d16c87932d700f848daf5e25e19013125ff9db81b83651e80b8a3e40fb1c973385368a64eaed47d5cc02b65ab921f789684b40659b6c8f9067296e49bac1a0e4c71f7db49de15f1ecd982cd106782dc961047742be2c3a1f23c32acbb705882b32a1b06b6cef0eeb2b23d52a0d56b651ea3dbb0c19c29e5eb22e4e5f27bfd71f3d9eb7d74a9033eaacd6e642cf20b358d80f037aa8c57ca623934eff39148f19269ab3ee2207ee13a1613f02e5019c016be0366bbacd71b04b065a74183152d9eece362a0974cca9b4fe0a042c9def72c2953170007a52f082230b200c1b1a7510a684324a56458dfa8135b04583c8cfc78026528419725a4f69b8b5ca21761c252679d6f6447ec60578f4caa7864f1f984d5c0ab62e1aee1161192ec4d26b9da0700d00c6936fcb47cd87666717d74992de2feb9e3063e21f0a1e8178e0e5dd79b7814f2219c3c8ebbbe92a56200576427e643f4fa135bda29849fe721ce3c5bb7cf428b1b4576c145258e49349dc2e676807e976e1007963ebe4ce5fcc2fe9f60876fe0f7d0f140367f690bd2bcce179e9c8498efee7053be5897903aef5e3ab9186b00db14d855008061c2057136dd61c1092f7e7d7a123d1d63b2e48c89b339d42670bedc0fea7f010000ffff8c9dcb8e26c95284f73cc5a8d70c0a0f0fbfc4bc0a627398113a481c362c9010ef8e2c321b9d328b9aaad548ad9eaefaf3cf8cf48bd9677f7ff6da14c62368a0e240858c70a99526d73099b08c70f60299c391ce6e8001bdca66090bd63dac613baa7be9d747cdad391027fb07b9bdfd8d4dfe44bcf7941b682ed86805ef00568ade97bdb6f424b363b52e09517dcbfb1adaf7e43a7946830928ffc2ad85c13d3c2ed7879bd037513da072e5ed1d2ca75b7c11b98d6f38fcb3ec30f6b5b0faf22f7ca9cfa9016385cc010c61e2218d49b40cb526ac6229919372f43dabb3dc929e70d94a3e2bae398ac5bf95d5c35cbc4725b4ad33825447689486552cb0cd59e731b05757ed02d8643364597c51c9c25556c637fdc69c8b7b2244372fb165e4c138f265857a46267879c222787f7b5b4e810ef451f7f4ebe7832e10b04c5333bb7a4bc4e72aa04b74a9c872ffcfe7988000084d5e9c718fe505113ba21f41c0a598fec48cf428c2966de66b7ee2eeb313a5c373d07de244e5cbb5218bc6b92c11822db60db0dc24f24137b8e881214e171c11403e2c33a9842d4ed221c2241f7d000969b2d98506bbe296deb66d2941128b4a939b79d605148f334d6c9ed36b988ee0f63983458503e4378b702ab6a578c580cce1c9719dad9e70e203b3635a7462b033fc1b23a83ac94ea2cf2c48014c82618f7a4234564897d8b217c67896a3b11bb266ab2f5c266f1915801489903143482787fdae41f748fc6506014688527646209425955733c5105fd81bf210edda3403f1e11201a03febdaabbd8f6db5e8afe1b8940a05d47869b4b81ff8098a4993611bb00a8a1c62a9faf3d43bf4a59a5d07efad8c2c12d03abf50098dedede8b8c3c4458df93c088462884d84a95d2eb9cefe4f8ad1d229338e500d168534db6e8b3b63b8e3884815e1ecde15a2ebf344a5ccb544a30d159276c086228c4daeb51f3159aca569a57809b97e6f0bb4243564b2ed7b9ec07016136023a159b0093885acf8709e49a809621fed7b08162d7cdef44e2fbe9db1369cfa516381cf261a68547e344484975ca2120658e2cd19bbda2cbe4ad050c52698c9a304cf1661f24021cacd2578ea3a38c03b6c2ecee91538c567fbb2a385d80a0fbf94b91007e8d861d79479e9d8f0412d219b2d7814ea0b8fe3abd0ddad35bdfbae2d99dc68ce84192d0eab57ac849b5b23c9c8e3f05e6c10832f748ef26462651576fb6aa9bc41f6f028331871025e3b4568180d5e62a9f0570943b6d65a23641f707c0a6a3dc00a5a062a96609c7dc3efd4a8d705e20be87b8af66dd494ec403c3331e440dcddcb741cc3aba0b79642a2b28bba362f4178377ec2445a175b7010c96ce33296ca518cb24c08db79f1568d150e1f5259dd727b21b267c9022790cdcea1d23e29b7e2e92e730ce14c7db228e7aae779c6dc4322a11079a7a9bf852c0c7e722704538c1cbe6dfee6c953557eed4e286dbe6a2f7f925711a52b0231e0f764f48da8c9555352e66b4e1780f8ed6573174b028682970bffb0b552504648246654c2a872446bb11cd983c38f47c023cc6f9585a4140d7fd8ab6bb77615174ba2e7d4e2cdf280566567180998287f911932c51c0b682296808acbfc315f02d8283a6df8144bc6be89044d0525ec4329e562b5607c88afd6a96f90d96e29f11a0443d9e6716ff616bb2ed348c00e8b8d5ff08920e057301ea00bf14ee026629d06c0bee02eaa6cb0080c79ccce06205b737c8cc07ddeb8312ff1a8205f81b1c9be203cd12af46101e4cff7b34d1e78656dd3ece7ab039107df4fdd17c60480e3527135265b6d2ce86482d2d84c4862690208ca37472c8d0358e8635c5a748fe4bb5e27afe39f907ed12d96d71c4be4c5f38cbfa913bbee990fcad9b80582d57c0aa4d512e304996c6d649b8a6722a3dbb96fbf613bb0cc6c669e8c27d097b7a9f32033532d6b05fe182b3d918bc11d3a7e27914bc0579f5b914fa541e188333349a2cb6cbc6dd8388df7ead472f02628578ff92b2a5a6634bd0674334bc4aac06c2fd9eb63d23df86946ffc3cac5fb24710544e2fe5563f5e0d760f491acc51346cacfc6650d03feaf71d81776481e8bd7a955270091cf792cf1577d41abfc1c5f86b775357b31265414e216b5dc8a0f40a070160730db84d19c2d260ee7b76816a50dfd332e13e3b1de1cb7b05b20d036d9863d49858c6ac1330b19022370a88d7c5ec27bd7547115644de229ba203140cd6e73012762ef2a4fe8c9a7d30e3f07175277e330be85c1e3ae09d60afbc4a1b0698900118be32b4f832b5da296d03187f89fd66ae81bf8a603293d25ed09c67611171f346f4aad7ee591811a6543cc24571f2b16d88af48644dd9bc1fb1583d73bd0572c8b4b685c0d179c1622e34b9561c0bfcaabe5aad23ea6ca39c5f2347a6b769b9d6995204a57c150c7561df6303dbb818c14f01fccf5a24ec2a160826c87ca2f458173f559cfcc348186e2546027beca139ec30e2b48fec626f0a0fcce052289ecb6b31cd948c25e5c7e71532386aa5b0e765c3f96605e870f73add5322ec25e155ff956b8a819035d57944f659877cd9daaed3cab058565915be1f9726b371f8bc84ef998e0f00a9e02cc74b90faa454bab30d55f7f72d465468855e3c75c871b5de52dd74dbc4a084bf4944d3a942b26f39eedbee7d0a085b95de0000bce78b652385cb79bff14c59a271796867d92264d035889d01da9b6b68dc13b6e74ba438453ba71fc9c9efa99534d3ede7b703f7e1d6e9ea25295dd4046e657eca967de8b3c3d8e265bb16416f30989b3707d252e71590e4949c24c4f584906248d4b68f84e578c2480d97879f1f5825e41c67f01f5ada08584a1f3b45f48ec6238d1655fff0875563bdf7db008ec143d6a24fcce22144530b4737d15e5abc64542b62465eee854d99230168c311289e7d8adb7247f638628ca97555d124cb2624fb91996a162a107c54e247dd7d7b6fd4fc89f8edc2016941a22f5525469022e79ce2b4c12b4077438e7356376cb4c6aa070665d98435527f20a0019ea92be1bde5b0ac47039db0c22eed4e85740735d4711b7b9deb9c307c7a45d5ba82b93e1f2382a22e579483365dd252ad7a727d22c888949788b450d695bacf1810a3ef53932945adc69412a6eb2e6eeca191742fc45e45bd817caf98d681ece004b4093b4cfc2e6db94b1054aa9cbab1f005871c4d93a13ea521e5e3295f4d09457310568e088913b6e6ca4284a550206f8ac2f71d94f6537924fe52b26067ada96cc4d8482886dcdd70ecd264b8ce4645479808017817f27c7eb88bfeb3313fc29567748997401bbe26c68dd9cd8e998f98483357471f5031dec585251dde0e69fb0b7409275dbccabac02a139d717138ef7401d38cfb8ea4648c5d268958be8434427af067a4709db1424fa519a3107288e0cb06c9f6d099765d1cd5caed1853c725ec4dd483d639fb69f853337f6fc82e44d863f8e238186fcbea1ea12a41780104ba34620485aeab410d2fed366ad6620ccdd9ba422c157fd09a08b6cd54fe871304c33b3524282a052b4efcc160d3d6daba979a5f13c05500c179cc19100b704bd5ff506d6ee2ec25ccc92a7660038e2b645a00ea621aa457d91e057f66fc8c67918fb74cb591f6152cf5d53beb8145901b5d4fac27df06ab056c838c5a02196acfbf397772f2189147a7ea95fdd36efbae709a7957e776d6b315b38def0ba2b395e0586a845b50417231f22935e0d913e8b277be351a26d89ef843ec2a3bf03671ba0fa8470e83a171f4a098c24df5eaf9a91878b05f7b53c4d86791ddfe139960f869e8b2ff049001ce98c522dc7405ea09f1ee01b3125101c26b76fd9a1d035e6e0990108ef2617e16ab530205a656eab84eb77ed7680ea179b1510433ad569b5ba621229fb5b5cdea9a2c62b7e16232429ec1af9cdc2a7bc4db56c2e503a75ed0621160bc9c042ca3fd7b97f4e42fa6405ef8094732c005661a0ea7f259c7eaf4b8bb01648f4e552f0df456b37acfe7ba4aab66ac6901a03a1e325f46e4cca39c5fb9871e4acc7d9a3119dbb3fa6b53c8a010d543e2b1b5849ea4b8bcfd8abe157148f0f1c083c029c853dd8d0d1085a1cd687610db245c5caa4aee7e5ba86641929ebf6ca83381f6b966db961db56844e613236c708a8c9f9dd1196cc13ec0841384b04ecb09019fb7558a92de24b97b6907d0b1ca26bb6d04c3143148d08925687c64a2f284d59c37445f46152b193097d3bd03bcae997ed7ac50f33b3be14e93d55da0886f0ce274f43c6c3730e1d8daefd84d3ca5412d3066610a1701276f5de21ea77ec0d73308818ae02cdfb0e34caf1e73eb847cd3da41283fce9127c1cb9f7e471e42d1f013bda5db25e0b43adad229b390459fab64cfef5b0498be74fd5a701ddbaa485c3e495536745379c4e74f460308878799e1b7b4135b2bee1a4394f774db62fb4217d94c382ae42f98915670a94022e2f41c422f91eeb6ae9cdb6380570f2ecc178acb1c079340db5462482ec130a0c3cd5d4b33ae16ca0b16b651d9d387a9f91702d1406f585f8e77912612eb1ef985627846102ed4a3c9fa65cab8f93fdf7edd18b0d8788171643a80007df252034648aa7a69bfe551fc18f649a1d8a956c7f09137b7496d94d8b93bb42fa1e1879daec2dac8d05d826ebb2b6f925816c1d3f8f10166f9e416fe08e5b7c93971dd898c0884ed9655c917bd0d626f4b9bc9fc71dc6198ad75e12962c9bcad559b535e5c8ba117db3bf31faba5f74800324d85ac940cfb2dbdd5801da73a648990d4297292e0879485e6bdac1e4d3acda616095bda3b2998060ff1804f73af9b012e27e65e2f61a3a84bf9d819f7088269e8866b891dce19fd0cf50e717735faeb11258f7438f282adc0e59c1df6b2cafb22d5e9e1ac50d10c25375cb0ca43033a81811fe29af10936e5b726eec7656f9a3c26e7e075f29068600bde20907c8282a845f2b36cb5b6053181706d8f11149fbb751668b019cf0cdbf5ecbf9e7fd37d4d08b63c14bb41ab82d2713545146ec54925577f4349e504c880c397f6ba7674b90723867a58b40f2bd7d762cd1b25ffbe2917013f0ae02e8cf2ecd29623fc03bfd764b098cc1fb644fd5d104dfd877a23334f35d3e65370ed4feeaefe88930c98586973e2f6aff9284688bb95b92b1327b2be89819c3ef8358820a19002332626ce2796f01e4a27f0a751a37bc3512e4b68194e75f16fb44755bc3eaac2eee1399aea93313f2ff0a890e2270f90347292841f89046a65e8aeced42de112fcf7bf4e2a52a5b1766733db2814b1499efa1d1280d5a0955984adcbe33bb1fcd794c66c71be2568239b82b0def093efc1a624091e8d79e29fe7e8c8d6da913e8c64f5ec891e71f0699d6580ae7361fb240c062d84ad02e13226f2e56f0ae2899de0881f91522d49aecda3cc7ba8aa63ba604850cec9c322e84321f9bc7f700edb37874e5deb263b3dc387eeb5b3f4b1fa7e72d12f8dee8834904e2a7733573ecf6592201d1f7e08abc610ee4991484908a3c11e2f6ab11d970256882c9a529b85a0e515860b425c48c14509f218058b03ad7c1c39ae62d746e8910381d36a029aea4bc2c001df983f5daf0da4ad1069a193fd01b9000fe1d90c238a7bf7ff82fe7bffffbfc9dff79fff0c77ffee5dffff8d7fffaf1db2f3ffef88fbffcf1fbef7ffddbbffdf8f9cffcf8ebdf7effe3bf7ffcf6cbff7f137ff7977efbe59fd948b29773149f79d78aa55fcf1a71f1bee360963706a42ac6a155204533aa0187b54fbe17a03fc1db5014951b3a013959b7094d0bc3e39ea4cc9f01d9e2e0aaac01f1978fbb91e9c9d367d03d58eebf56d868cdc46520dbf1c50d3e8331f2c4d1aeaeb0b15d80a74719d4e22ffc689bf9f53d82836b42e82b6b4a8180e2714a7e2fec7a7cf41c2fe4942fcbe2126904150eb4c962318a159281ebedf26ec6a07f8b5177818b212492893b993bff93162c8ebf7354f3c40b00d3d1128238f06b2d716d03e8a2d367ac3a3581c44b5dbda7955eb57413d4128e8ca1ca16052ed4364363831bcc427e9c317c682e87fc40bf052b56551fb59c2fb74a9ca3862123e31fcc374cb15bdc5a692ed765d552a05b9f58382e72a0a895827fa0b257d4fe4a04dfb40e295de8b498f5a4499c66465b4b3b86c98e7032c1849eec160e7c610c1fd84a27848f23c4520439ddd90d8bbfc32f95e28a6c96b560c1e31edcf9efc04530a558ec14500da422acd593bbf3bc1cb19b16a3ed0c049aa9856c84820adc474e5d2b4469820a36d34b38e67e4262f9a0f23d43f1fb07904b751ab26c2f3901d016720bb2bc171b6f02b997bc38f4a1d95113ba489989c19e981cf502391888b5dc10a7ef911c935d185cf3098552447c0678f94dc941ecf6319477ed08ff16f67f4e6180c0323375afd0683842d58eb5f8058639f794014842bc534a3398877526ea97c159b30389dc4a71c296d74dac21087f1aacea4b8f9b060080761178e1eb96482640d2fa2206ee05cab2280ba11c1982387760af38400ff98e6c1b443b9c561a773a4d1340f1e0f6126d129e66f1d6c18ec8a8870471950ea9b5e0979311cae1bec89d04b29cb102c9765bcab4050209a9eacff49cf6da7bee25a233249637cb63f3388899d791c86b1e2239807c71b1243dc3388115f4a4ed0c2dc4a4e8f4d3bc3f00c45c5c53e5738918049a7a01b123972b384cefc09f59efe598669a224bc27d0a8624178635b2aa7674822c85a8b18ca1d405469750f84ecc15ff028928110d41b371014a64a92d70ad50ede3d15e8b7fb9c60580677546f41c50ca85d14bfba82d8322a8765dfc9db87c5bf2c31646607cc2c49358c6c85618269365fa70866b8cae876f99c2e195c83319a47270c6cd9cbb6a9aa6221c6e82cb46364a9437d8e589b2018d06f828fcb9000ae7813060cc3df8d37a94e45ccf33834b21242c1b4a05c7ef3a157858fbe696e8a9319e0743226537dc86735ee2f88ce36fb46c7d36baa08588e5a4db9b5b07076eee46219c15620245220d13a72aa25bbfddaceddcd8bac56e71e421f3546291c013321ab9627bb082e476f92cda455014537a6060df4a2db3300050ff08a9dd470cd7b117c1d6ce75dd80de50e8eb16154aa6042e4cb2eafdc4a088c7714de17be2ffe6d1f4069545443435a1d154c979089bdf964f08d4798536634aae078c812d4ace7552de364b6f0b593c9acfb3930d1cf8fc5340e7031633ed5a2c7ad705019f76927a459185939737dfb93af7cd09832bc4d5dd997d4f9d7a402ab4859858c6d51dc24d5c4e930d30fb14fd6ef422ec66c056711314adc98b5470b287babc20b793711a247cbc96f19d5b772a8d172a77d5006c6e11f9c04fbe7b84b016017fe5791c02eb0507bdf125b0a2a9b106d3b866d80d55b0e82d02e66309e4093f4213a658fdd3b0101063a4e56016f23c004899522213ea224a3e86157a42e0df53580ad444ab2c2f23e3608311f65492f88a2d1a87d01fba318fc8ac11ef2d7b53843f978c8b37421e423ada9068ec05753d431ff2a42b8af8c2f705f6027929cc8a42e95865e232734c5ba4fd035678481585b8ebc128a16b19b9801a98ce6019bf20d3b44bfb741276005e31533a58f8e11513882a4836228095b4509e100e2b645fc406a969b6cc45ee3ee248a86562c2efda6730bd53deb570e64ca67bfa8ee4b1ee46be012722433e605cc622c1d197a4ebecbca8b737982ca2168af19136f93cded52533b7ee257d2fc0c8ea9705a2d6bf7192dbced5e271417a63c8f420011fd36c812963192474a4847207921e99365447962f1c585802b5d074e38a0a4c7dc48f8b2e0b2f12490b40f59772606d38107856bf40722a3e9c62ec21011727862178e2a928b1d80866537ccf1ad143d48f996d1214531b13183e9c72c512f2649c9fe5df981b4355250b6fb006c6e0735c9e8a9fa50b10b93ca3de81d1244b06f05073b310fdd1c8fcfa5d2c78bb0f2dc89a8a03c05dc80f067414ccadc4e01d0f318f182610d48ab4cb1e329cdc86be951126dba6a67f2fcb4e81f5ed88e40a12421b97bb789c8c491360f688a80ed5ffd3fae01935952da1b7c922ecddaa0ce5175d2722139b48d7d94180bb2b5f4ecc8ff1312f6caf5b65cfd7cb3013b6e98b37e224c92b53193d8d8b40a04633e95e2afc5f3f1d6321704050f978f9fb62e30e56b726eabd691b59463c99e92ef5776cfc12e2b7f5b363930c18c459b36809d89ba92ede9a42cc032130b7ea99b14461a10886dac06a725951eba3cde72757ad8544e5810d0417e808d19c52352f08b7240701ba85dad2801d2df16448201c7a31a6a86bd72589de0d49d9dc0f61b5ce03aa1a5b5258c003e9ece4de1a24063e59b5877cdaf05682fd9e706ecb8d0f7ca42987df3ae6257a8839c7514092a5ec1ea17ee37203b60ffe506043259b710a596b625b81a67d68b3dc9275765d105eff7764d9464b97896dfd16aef5897a629d3baec0662dddf41d739712fd91bb31bf7358cf91101ac61745e86bf1518a5fe24370df82de5307a7e8b0ca78261f01af9824ef964f61f66e1c713c26c034b4957c3945850064fd2ab52a57e22288600b2e7d7178c2af21586b04e3689a14242a85306b911363502d2b849853340b85d113cf940dd9b3a2c37a777734773cb1087c10c09ae192d0c79b9157a55f7b4844d265b06113fe44173ec612f7d575dd04e4706f01db620f47c70018babd58f7838919ef90009ad85cb2410e99936bce79cc104376c4587ef38dbd020f376f8bb68d107ffffdc81948bb956fcae02a9476e280c614821e55ace1457cb24c8036b4eb9b0573d0ccb0075d7712c7183fb159642ce3c210cb64198b5d0955bdb9e77065a9008f229634c4c9b809a6145a83d2d4c53d2e823b5fd62e1e40244fd610e454c2bfc17e1ba449b6c8d451ec5097eed9defc64e22942e2b4306c11a55bfff80fff070000ffff8c9d5b6e243912044f3440f2cdb8ffc516c6ca19acc258907e1bdd33aa5226190f77f3fffff70dae4691b97f55615ff85a8a5a4d9e5771a3fb79b6f22e3910ebe6aa6a9d05a730e12837b3fcffcb142d0613af5c854e38a92ed1a14b89538aaba72c6f5010a32fa7273071d2ed03894bf889fbf34101826d283da4bb3e6dc8d8c7b524f6029da1a77947f3ec98df2cf0f8577fbf76513a1df7adf203f632019ce6d8d25c565e7b0a211773c873e18be69f6fc3006eaa9a2f0a3c8f011138ab8b6738df35c64f31f74bb0eb5dc0c9bb6ef0c4506cdd2a831c398bc8e8e3645de9e30438668d2080dc300d65f7f8191afd5acdc85a978295f093566c50e707cca7e301f62820fa8430a7ca077871954683248bbd95ab9502d33e6feb3886a5fcf510d768d519e2ef3d1c3973a0b53228704f4add5babee220af5c8177fd90bf348e6ac13a0b29d4f1d60650c3f4c8fce0773c802451119fd726d5c074e18d954e30ce400626348b1f45594520f94516421aa816c2ed8203f42b8ca561e39efcbc99250c690540aef0b414eabae4372e72ec35ccdd3bfad24cad1de65c62e1904c54a5e2029c9e4377a67f5698df6310b6567535224bc29b64416e8ac395e063990da8e51b4d5e07097ef82e6d4695904a07bebc9ed053aafffad5c99ac9f7d2810791232ecac78aa95f16b3f22dfcebd9e9f15f6bfdd9f32bb6a21594f7844a0c0feccab2c10636987ccbc57f77dacd12d183ff82805d8d1792092cc3f436b610e0cbf4c7db45af81686e1476b49808cf47639738ae1661e5a23c72d1693a091ef0ec3a2b396fffba6304090395b46b9e3ea02f3af4128a981cb03edabd694837bd61c648d9d69896cc1d035e45fe575cf63bdd2ad4741afb6f3f6aeed327c84dcd49415ec8d23a05912491e58d63cdab4dc868db20d40a7b5da9a9db7b932158a2dd790ab18e67d4e02f11fbe10f8674049cb46d9b966d6ec2164efdac2921ab2765eb550ee6811ce2dbfbca9e8ec0446e6e3d6d25998e62e6802adcd06e055a796c344184f6579d757b42da759f9892efe148351bbce2a3a59b341981475e1ddbf9cba1fcce223bb0538325a1ccdb0b018954402c5ca1c36f1c2bdca23bbba58f309ea93e4d0efa1f0649c3ddbc556ba84adb3e54c16a6bd8299e17a44c69a378478e3f2f801966ce91addd7584f5e645578a179b1b2f7fa89087e675273ccbc735a2376542518ef8d1a4a47301376c7d2b4d859fe583e7904794dc03163d230bf6eaf1e5b8575a46995f656ef54600fd3f5eef75644d98fda82719e5045e46503d7fb7c65bfdc0b549c2de3deb888b6633a5aafd048aaa930433c4280095df8adc6badc1088011a47eeba439d7f644f8344144fd62106a6df5c00079b74a5eeb67e18fc52f7b7216b6ba9983df3473b7c21d5ba6080336de6044fd0d9e7f7b7f70bdc463fd867053898e9a4691019b8eb2ffa10c400a38866c44870f91b273827968dc378e16a1867cf205490d07ae05af9efdebc49ec5cb324a4f09cafbc2d24396d2c6594b5e3b3cbfbe0d21fcbb800ae6d359981147ffd41fc5221fc147bc96e5fc1174d63eda54e273c6c14c7f260b226d224bdf2c554a33dd6e3f6823122eb26bd6b24d2eb9444bab7869a8658919bedbec65c79ff82a5410ca793719c753ce593c8a3eeb1ee9e610c207085127a6af4be9baa93932fe1f0f91e686125b0a12dce1970ebe88ea48e22bc4ccc2e06f5627671d5672fec4447211426f571963cb7d24292d932e877ca5f643be5841d59e0cb5cd0f4b90f76253ff8c716eeb68fb6a0c86fc2be442edf43f914fd68d5ddb7021bea336004ab675ae2fbdafdfcc20211d069c403d448448656ea6ebfbb84a9a0ca1246b7f6d29f3c4889678f7509b3dacfda4a352a0cf664776069f71821053170d4221b65d04eae8cc91c44c0e78befa8bd35aadbe3c2a022fc269ba54f899a677d44bb4e45df5cb708a40aeda9f5779f60f5b36711c1fad29c0c7aa71ee71875cf9ad5e673344d992a4199f2ebd1b186c6d10d09d6a30048664f2d4fbfbf58c3b3ebf155048f93f390163425e6d2b07e904ad285110211d0f4d8cc8552563ce1da903c646d5279a2578df156ec98596508fcad9b710887758466f00cf1b2afa981b8d57f005081b32ae2c9805a2fca3e6382f0428804797177e901a6140e15687926d835de545fe8ad8650d3100d1ef908503509f3ccb621af553b73e48bf8729d7d81b2cb573f86060d113dec78200916e5fedc044c8524208599422c2fda8d934c7761784e76a04e7338586869e4028a76fbcd86fd7973769979f174485b8872ccaae9d544c5bb7c8fd494a20832c28951f8aa932261320b915b3c6aa45cf37daa568244329d0799c5fe4bbb540fb4340f944f8dfe6488731f7b3b6abcb69f14cd970011452ba6ca6cf632b7229623cb17065318b53ad7f6709f742a056a13b72edce665df82c06f79e7cad2ca34168ec42603063491be87f05dc067f2aeb094361c974b4e52773c417b56d60655d2b775e2df964ea776c832a286a0ba3a217a9da0d647a08123584977fc5cec922e0cfcc58127945bef35cfd93a9f4cbcb246a32f56c1c35837b7247725ca009660e834cadba20838aca9b50a184a1096846e6d8e28a59836f8fc4ca17b576ac7f2a7f1ee652cf1acbd623aeabaf14a0831414056768ac52e4b8cb6fbe26761d797f998b08c36859689a640171b41df6374a995ae94bd8f060a6b6599182f7eb64bce1ab5b54c68aad83515377b87e090ebb1439f1fbda74a2de4c4e30f32a7b23003697a432a90e1a80338b3247810f61eeb123bc8c8ec3e4ba498bbace27374acf8f9da7d2e901a9115fb1dbd7a1e14f1db2feaed1fc4638acfc34d2df0cac5267fdd5a3c1d535fcdf5019dba72b03ec4b796dba9f7c510f5020549747f5d4bc115eca0b95bf5ff2bbc5d4fb77e7b3eca8523ee2f64f586565e74bf23a31f39e31e248971e958a78b2beef121f1e48be8247394fcdaf5aeebb13d97b4ed41bef1f85581fecf37cfa19d717787e8e7e41bd9be832367dac084e84674051c592debc137081ff5e54cecabb46a44edaa82852768031491c70eb347783c8b3cbad792f03e71c7590ff559cc48be47334de7b9a573df33611469be59192a6d6364779fdde56a590acd036630455b014ed3862009fcddf99bf8f2bfc0b61eba758956ab790344f66346a353a60ce7c146671f2c89d1c479affe91f8e995f1df851ad237f138915aba5eb28bf8b3c55abdbbd9cb45f7473d59a616eefc2a2b95987b9ab15372556dab45ee4b6be52cfe4de07716661304611e4a014e1839e1b5c678a62292c953dfb93285c2329ee19561f2fbbee57d0b61cf2ac9e2196af0b4d8ed1258795764505906760349fc76bff87beb78a437b9b320cb892bd304e8fa536085ced6bcbac9d091da844c98cc9bb8a8d8de73880498f885c3f1d67a7d4575781fab4e194b22fa93ee1d4adb251fdd188349755624ee9f2caed77ecf543c23658010ae5f75ee6f879072245e1ae6133b51a8dba284f662219b593fa5e55c1ec0dfe69b27c7b3e4479cc0a3ddc213fcbe5a71fb843a36a762c75cab6b7c3dbdbc0d787679fa6340e47dc2719e2d804b9218938c595b0ee96a2332fe192b758c1cca30d739aae61f26758581dc4cf8bcce7dd39ce454d0276a3e8b51b93b66acec7109dd5bbdf63cab9b73cce598edd232446130f90e51342ebe10af87de4e64aef465754afbde7e130a7d99257d158281d1840492ce59c23235e29e3c43c3e6fd36a5dfc12e33bb6688dc9539a5f136f7c610bfb378e78be2bace9355b1ddd43f17fdc006e591bf6eba366fb2e1a14ac3d0ced9ab3a6040c669ba297ad41d7ec5197e45cec6184778214bd48cee2891678cc0b8e0dcead10de2befa7a59e9adff484cff030000ffff8c9dcb8a753976845fa5a9f90fba2c2d49f52ac6937615a60d6e4f3c3018bfbbf9b4771a6784b233470d7f67e5e59cb3a57589f8e2e3a03aec0bdd06316f50b12f481364e9f6a3322e1726bfad3dd2b7668f288fd84a67208ac1c0fee8f77ba87e66b0491ef67ac5662fa8739b9e98d69487d9b96feca0dd65da5f0bae7a2c930b411ab5a9be4b8e5fd729797a3ac69c85749cfc096e859e9b682c1b492971f58edf7ac757e4f89a2055a548cf2c061c9e0e63a2b33d537fd76470bbbf5717fb3ceb39c45a340b17b8835c3b290046923b2b4853468c91632938b0011f362d5a63c4b0341c2c99f587b7f76854963a456fe8c10e7476a507d3e2b5d765232056b39d5349db63cea428b489a48577e35805c2122626dd39475e7c5fc266789ff45adbb2c1859960af2caf37cd6cf9549daa88adb17ddf1dad9b61b4573839ba2c847d57ac8482fd5195930351aeef3083efb5b935f4da9bb2be6d20b047e6560d3f24fc50e23d83ea6a32c4fbf0e35a6bc4582e3b6b65cc6abe9168b8a1ab11d9e8953eff0278d2a606a79fd0034dbaba3209b12a250e20e580add18a0f8d9dc97eb75c39f2ea631906b6aba999da51f301c1d983289565f1b578e0a8431d2a88f9a7aef233828f11909efa813757b5b4e2367ca6726ba82cba91bb1cc30e89be76314923b10bbb9b93ab5656c17607e0ac68c33368fb2587e9aaeede6378400c9a99e9731b914a3ce5292e46b333af6125518d24d4a77e87187d9a722ee3b006e17408aaced3ddd073ee006fafbe485a23f5cafb0a43317874159eba48afb6a5a8c22dbeb444b4538d6bf51441ff250a9f51bbfea258eeba66723802e20d8d23fa461110d96359e1f39438767f6825fe7876e216f2c392d04e38d295d5e58dc5b7746db4095aeadafc5e3730fca40b188259b69dc62489ed72a18bdcec275f78736869d3c615b7cd485d4c51ac021dab5a9e595b44eb6a7b71ff143600f6661deec7993e4d8bb43107986c299786c2b591739a4ef5b5747b6641dfda13f287f56972bdcdd02a3dcb86fc3b9fb4dfde60e2cf2d7db4653bbe879f00c018513533fdf713dd6c33e375b2cb6c157fdf3e2a6ff5fd69b1863d258388629b138e308f41a1ff7441d675e96e4e91c74c8025425b2fe023d347e48f7bcc6c3428fc6dc2d2768ce16c78d8ba36d8c4e1b7bb6555b4854d59876a248fa52aeee8e8aaad866b1956b0af3d9a8e45eb9ea154f73b886fc4ec96228bc77aa823133b66ddaacabf5e7757d267db28ed34cb717df6307cbd5fc0d2621c81bba80fa5f5ac960feaa8fa0a273fe6b726ca0fd24bafb624ad31e645bb4f39bf8c4e401cd2301ed698b3a5b9335b239cddb8c78118c396275ce27b2de34c9c305ded6298acaa0888aad8f7b48b53357dddb44bdb468f40336db9f7dca369cde46556ba57c66c3fd91e3366b36c5e1e97745efc04567611e9778439d29570f248bc8d83c25ec3165177f6f98033389ce43f5d5f02c2bc6b561469877e7a072587aa02fb9cc82e4235ef172a874f3a5ebb658f34c6d3a0e9b577817fdea991160d7dac8db26efc828ee6b59850796360b2bb0ae545e88c0f31479a73e88039554a967b14fdcf47c46a1a140af2215dfab2b6250816c63fd31cab6b0c82bc1c45b1e5e4c17c39147973a8eafa64b2f408f75f3209b56dd66d91c19b5db66ea89d9472459afdfa4a0cd8f02c77edffce506bfaf50defd7c480f7323bbab132ef1e0fcfd47a5424d8b455283590aaa9a0f3aeccb042f0d73503e6b9677af75a255b10e964a79ec6cb5d8fc26762b84871b4b31fd2f736fbea64c963938bb6da2c8ec8453b53f45bc0cda5d25661142b5bcbacb19aeb23a6ce62820a0a952886a65164db0b1e21bd5320ff898cd538499daef982c9cf5dd5b96cde9e177c306b73bc0039d33e0ea5951d3a138e3ea7bd6fd1dbaa96007d9fb601ffb78bf110036a6a99dc45b0f1eca7a1012946adc06874123b536553f6e31ad69b3130a34deb0fdbd03eb04133497ff4ea728965cddcc5374a4187ab42965add146bd0b55f5f926e100875dbe3ce366268e0192dcd34f66cccbdd2e6e72784eb92207581413ccb1c7beaf45679ed37c30e708e0986edf33b56c723c7de7d3bcb6f8c6d7bc8eb6f80046edae94383d19c7ecbc5625850bee95c1ef93868979a52cbf0c3bb937343273710f45ab6f83da49db06e9af5ac71f2688914e279762b5eab43319e2e2cbb10de6a964cc5369814fb4bfbdfe1e4cd398caf5eb617d98cabacdb2c98aaebb63dec3d219398bccfe38be721edcd419cf70c08902d6d6ade438e66e21a37723cc52bc32ddb31b35db5f1fd6a63ea47e6a098ba67b2a229db4b8b5adcfe19c6b91ea3e95ce642e4fe78f8367196df29a7cfdd34ea583a2f9a8c5a8cceb8eb36e55466b371f009d6d680f53bcca951794c9d05262a2dc58ae4dad3cc068daea09a41954ad346ea88cd905fd9ddc8f0e073ecf83b3d1e651b54ab56c4b1f507e4377a1612cd4ca9c5bf6ac0e1aa474f23a535e2409b3308f0fe11dc1e8f889ee134733a503972573dc20115ab6d64d436637e43c378ebc4650e2cdc6d7b3a488abadc5a66e468aa0f4ae282b69beb91cda88e05a59f2fdcb567ff079c5b068e46c731ddd34dd1ff559a0c78735571723f34f30b938f6673e02bd61f8e20c01dd3a948e6daaf2f2dc3f7401f02d61894dba919165a76a599b54046ed5adc86d54c5300ca86f8153ecba873cf8b3cbc192505fc55a822c43529cf41c01c40e935d7d3a16f98c42a5203f8af512e4a027dd752c1c24ce3d1aeb4e68b27ffab70b35b6c2b3df0d69a99bd98c2b26b1fb83e2d93a91389f8a3c084d631897ae41ab3521dcfefeccd04ce4ea8fa6810baf77afd0c0354813411a16a34a68a70dff2b692926b90b0c85e2c2cb9440509a0f2ed9b39b28e4904b04ecfd446751fbebd4ae06207d1860c6aa515b34ea325e021c4b0ec666342beddb83eacd17340f99711ad642d57bbe7ada2b8fa131e7beada59d4011d9414ca4b4453506d17ece185d8ee4c317d13299197cab9a3e73301126e2b2817695e8e5dd47a62539fe797c7456aa9541c4ec34a4221587f11f15b73cfe16627548fc56a9ceb6532664c1302b4ccb96dc2c8e5362e5deec67cdd7e42f66370ba6a778475f636acc2a869378463b41e0866b466622cd05864b7bcfffacfe77fffe7f9a2ff7efff1b7fff8ebbffdf92ffff9dbef7ff9edcf7fffeb9f7ffcf1b7bfffeb6f1fdfe7b7bffdfd8f3fffebb7dffff27fbfeffffba2dffff24fa28ece6e2874680cc356ecfb54fc6aa61c256d3434183f6e6f38223c08059a5a5643ddee7691a233abcd61795fb5630868ca46643ca5d33f18f316688c71ce48088bcd5f53dd7c3d89f4468880696bb3cac12be666a915b5e970a695cc71611c03f1b41d2c7980b64826bcd85a7506597d4eb54eaf8c61cb90e462550d2029b3b30f4303e705d547e494eda3166b645d4bf2f76f299628d69b072813fe3874e8d599d52e7bf6d798dde4b7350696467737af66524a780e68b79dd148e0838edd7b747f778f03de46ec11deb31c011ea64473a7e2e2700bfac60a628b4f52c5ccd9054bad2b9dff39afda4ecb1aad6ea1dcec22551a317b694d8d1c47c126c5ffa466d10b1f88a8a15860dd5797869078b134d6977175b1415d3603239106539ce00f26d346f17db61d376c0c4a2d2be5e672af7d8d5cbb58d2d68a5c69b7b3bc85cf86b98f708c0b1a796554ecbd4b7a9c15f62fcf89d95dbd5be56c4ab66649d4b17182d89b9dab6ac44667435134de7a2debbd99f7445acdd80eefd8b4ce35ea560dcc5e75d7e2e6f39d10904402d3dab20cc67a9051e1c3deadc2700c372acb3e092b734f572b367c2f4ad88907a92fd3d3647b649ad1ec87c5671a9a65fc93e3351fa1656fd96c62b52b4188a91fb81339ec1aee84113ccdba5356adcd78edab97a5f3adc987c8d55484bdab5a2f9285bef178d6e1e0594b0082c4d50331f0a1d8cfab40b286fb8b098eb00957eede764bdb77cd34ab75598b0a46bd65c492ebdaafd76199e06862bd2d6888664c723e36be229750c7527be049669f4d8351cf4edbec5283d3dfd46a31c8dedbbe560223e99578d9612f1779ad3affb99d142db3a4b73113c9b51408bb9b88aff26e5b99753f95fbe2fe0adf554ddb69b723ed106f1b2552d13d7ff6bec236cfad5850f91a0e753aabd543e9f8c11f0565bdd46a876fbdc50b311d708875a3e7ea3a38d906a022f54bbd4550c18d328fbdb65f480b75aabe1720f5b2df0885b2bbc9705758f8661e6b84bd80030bc4fad1a945bfb5f1ee6a67bfb7119ef1f8335cb0b7a019b819bde6be07f05dce6934342b8cfe31d70cbd42d94e2b8eb68fe9632eb2c7b7dafb89520a1324f3fc6ec5c01f41a45aa8c868d28b83c99071a359a0eb346ed48c6ae7c7888158c316e35445da391cd38cbb537114c9ddb919f337d7d55a175e4fca868ec94fea8e03a3472fe9707a886faac1657abed312ba10f45b607004999dba7443075db6a941e9e96cb384c852de162b959e99cdf94c59034b80a32e3fd6a8bd784c51cd3a2f2af3651c06024a5735df7680b1b41c0138789fff2aaee19eae4be60ed3dd1ec238db29f53e5ad3e8b693aa9bd6d0720ae85265b50e4a42fbd9dce1176ef63d8c16bcc9c8b32006d6026ba9199a3c3d17de505a6c736dc106370d5589071b68cacd60dc5ebf3b763f1261c374dc7c36340c0c6be27041ffc97756c886de85ef90b6870d008e4cbfaba2e876c5a019c9a22ad7c96bbbd2b178817e5f4bb45d2ef1734789aeb213f9707c208c67b7b82d1eba2cc3cef186f5edf3df95471d66c18267566a0baad8a8524d080dbcc7c6b201076a5a501da63ea7f00fd2ae9a8a413621be7a203e82bc1f9c92877d1b616e268c06d3b8d38bc8207b7390f4e8f8f0b8daa71a7fd61ca598d0148e84b5c47bf6de95ae034441e1848d09bea13808b472dc1504ce5d627dd3cebdcab2b2fb4e4d5b3fd6e86135411c557afa74a7b6618161d0add21c73a8ffbbf9724f54ecb6e704c593e2a902558f717f768c54aaff51df0ccb6d66be3b2fa1c7d084b629dc1a7223e541b33fd3ea0273f6b6c6a9accdd1a472eeb5526388db1ae5330effdc0d78d5ac613f1be366de9256b5e3e744d83a886e07726d5b64b651f609b342e655078ca2b89b7260a5e126943eecce3c5125dde83e6c9c9bd40c647f4eddc4b75cb9a438c1d7b6537bb6d91921987a14c34d1a8bd6fc9625f61aa1ab75566961e2ea4d669c2dee58e22ba4c46faf7b09cdaf84c2de8ccfa36cdd0290800c4d43b184273b5d1bde032fb5b310d3af52e97b700a98b569173d3539c6aa7d7a0ecb64bb75bc6fa3fbf6b6abcb6993dcf266d851cc368ec82340a79ab3924e48de967e7832aeb29dcbe4826c732fb13a00c5bc6fdb58b34cf332b35138eb71b1ea34fdf20c28feba577cee181fc6766f48639ca00bdb25e534397d5b4965a1624ca807db6a6c98bb69d920e42beca6399a187646338a78d5e91380ce66638eba804339500209b6a98990ade8f66e8f19865f046292c6af255ba916cb82182bf65a86eb41cc61f859bc0369d35fa420b6d9a7796eccf1be6dbf5f8d3c28255daf23199780e10a8ec62cc5d03ea62557131e51ed0f66aa4f67237f302f8f2bc366997385595c3162d8e1d99b4162083d2a3a996ec160d97e14e8664bf1e279b22d68edc71bd16c91eb177d65cdd563eb1890a8d8edecf89cd671528aa9adbca26e74a3431f238afe05c82397839f6ecf633b348b6d5d5cf7740518c07a7c8fd1bb967668c8c33e6db59151e5d4c0daecfaa87806ccca75e8d5d6a861822d6155e458176c14b58e45fced32d5811f30f28d9fcb64cc96947d93c76d281d0c53b359a122c3c4db8bfaa68980a1d7302254a17e4f1062a16529560807779008fc991efe210f98dd2c47042a4c5b241ddfd6d4068ebce6aa85cd0242502c11254227bcf660bd4942b55b11de12b675da7bb03d0ab452d719f01905d9747688efdd9e7a3b631a2d6ac64996d03ead71dd9a9eb28fb9dbc55b847b4c05eb6d1189a6e2b6a9795b1558956b4758f019700b2bba16f17302e776466dffcc017b43478a97f17c2cb254e757edde542d8788de02b8eed5020c525e57bb821bf37f39cdc9f049cbab5ec7cf6d984df6bfc56fa4e8a69c6c356bdafa365716cffc3821475a772f1a3c3960b3af6688ee024bd7cb280c6ea61d8f33c155b324340d236fb2d69f6a204572565397af57a14d23a3391525999146a1669836f44baf3fe9e41e0f4d74ae3854d4a9e84b8567523f5af40b68abec6dc7e9c4381e1ea7300864d7e564aebed443db407987cd416c87f09a0faba941fbc0bf28074410b329d543a3a30ffbbbd8c22d7d1213f993a38178b5f4d8e2801876f29509bbd38a71ed3edf27b14155ffc948b13f7e765da4add6aaa77731ad1936760aa0497af4864cddaeeb26ecf90673ee68d88a4c2482d1a9e7debf033e7b59b158da848fa813f3cb020958d1fb370bbae77c07e7a5b8226e88656422d25bcbea46cbed6133dd3c44d6e679bcb35e52e8d82e387cb062874fff620669a1dbc7331e282e4c669d92e104d0ae9aa460b6a53eeddb36afc4a1fbe84bb65b5f1eab7850f7b6fd4257bb6ab84f9d59e072537ecfe5abbaed5281d61085d9398befcf60f9fc56565b044634bddb2721ccf3121b9b4661656aa268b933a11fdd33ba69627548499054ef7a8702c48a651bb4797e33ab19a6ca3751b5e2e85621f7c96bb66513347723e969ebf3eb1fcc59012c174f4d18adfad01e72a0bc5cc49f85bfb23d96c1efe9aab729deeae33a337414e20473179fa856ebe3a7efc52760f0702836491fd5832732e656ed3964738b16786295ba31c86acea24f59ede30812ad6a6a48ccec60e78170b838f7bbee2e1a80cdae14d8ec46d8c0c2c370586e0b26c3dac3a966ed7994a66b1ce948e6058450d24680986297815341eed968e2001eb239e06e12cb6efc96b9e8eef49c4c3019ea111b6184a723df4afd7852b8563d7d8ff3dc964f3c9eaad301c253d5650e60d0e7d5c70b8d00cc861e747bc5d1172b8a05a2777c017b7eb7b57d11c9ccfad4fd7753343215cbcf2e983751c342545a3bd692a57bfaccd99dd1dc1d6d4d7ae4f275d2e3e2bd4419b7992a35218ea7ea63b7371908f295c1dcc82238d978ba6b05edb93ef97d14b941ded113835fd38eb3b1b464bc6b01ca3c7ffb6771a3cbb00e351686aaf9a33df53c3c0a3d58177c9a653c8c35fb6adb2c0bd3afc1dceb02985971c0d6d6494d17fde06befba16c264c46a4ae68dadce555dafb0db32b9828d57af4a988fd8b161f83f620bbbd357b1f6cf65a51e8b20abcd6b5f50967dda4f629d7a3cb5497c9f7fe8c0fa988c8d43a27b5196e11b2270b5695acdb679d3b4ffe46ef19cb3de178b62154672dc84df4e00873c191bd16bb7b77844b5020842808d7911c7ebfc3c76b3bbb425c057cd52a8735cd8f841a3db5d1cc8e651136cd7383431a949ca411fe83e1260bed9d17c3ff20a2f32f4538e6862d8f330e628dbb4f577310611b669aa4ba2ced6f076174480f5a52730cd9cf1a40c145f11a1d636277425a2c96c4418359b531b5b4e69f7a06f7e06873dfc4fa49726e13e990ed375a2a36e1bb722f859dd9821bc3dd384dd13fe83d1f9a12f9877bcc55e4d034679fcd3aab0ab1fc05492afa9e6249feb26223f07febe629be56854e0f3d89ed44f8e2f552da064091a52adee1ada1181b073c83056e6b9d24798a4191820f6c4bc5bfb340e822dbfa9e7dfc7b76e953fde7439b5ae51f519fb62c500b6b8d66d6890ac58ea957219b3ebe801a37e4ef37903a4687e2cfab778650e5eb63e742775782273f330d1035ed4b630169223c577eab5f5c6a6b730bad4ec8166d5daba792116253b48ddb97236c3a9b71be3b656a1e824274c25bb2b3482d1eb8f67ffb65637c5eb451b7af4236b5b08e7890bc1e6a56f6447dba4b7533e1f67fd16f04d2c8c9ceaa18fe589b73d955656a39d87edbbb2f17dd799dc5dbc131797e8583d73ebcd452b5d6c17189451fabe038d1bf6f6c06755d09f8d837f7dc45a8c4bb647b4dacc305f4e6e8b5996a322ded412a68fbc1029a81f964980b3cf89495b8176abb7a58f4af669a343c88c9fc3715e620f6e0c6b5758860d132da296b7120456b8ae6738af86e5f3b4a0c8749ec2f4f0be4519e490031412fb521cd1f459c76282cc1766d9eb25382efa1c2b75d2d977ecad490736d4f808014b977f5ecfa77a6062c6c12234d7fe750d2a7b9df211afd68b0688e2ccb77857862dc6e849285aea47a416d4c673ce9b212862c7b6328c0f933da6a03d2c1ba492903c745601a65505e237370084b76213429289eab0c5d7c5830c5ef3e2139e25b7673d56b49176a8cdded3865de6d97ef7e5735afc348be2a23679340ff624981fff05e76cecd9d398b4c37d75dc6368e704cd501eca8d0e396b9ac2fd66e1ab8823fd0ba1ea16d3f7cd587af137e685c667a9e3f9c0a91465b561e7211b04bf2cc0efd6307900eb6add076d1a66691a4a72d2bbe37c0d20dfe63fe9e4d1aba9960e56c580e477d8959b89c6c366c7c859dc2c5ddba14d29cb64f5a9d2a1eb42eefecce0865d45571b15aad3b24a0d21b855a6818dc937eb24e7d99e7027dfd93cb9a4be6b283661691a9902a3af5926e5ec35ad3e2927b8cee447db09e8d7d54a19ac3b8a0dc6eedba1b98edac94085c0c5f485999d8589af77c88c33092d047c1b9060b11d5b6f243ee0169972532d30a30fdd785fc51415cee1f6c3c42f19976abfc70ea6a56dbbf51c26818315a946ae2fc4e2f4aed0e9ed023769ee0b684b8b57efad7b643223c04b00d50c37fc4c80a8ea61a120eeb6c58641b5d44d75228d8c7f038cb98733c7726cdf5eb21c6e4507c2771ff85520e7f4857722b5b689b97362bcd23a6a1e70979338e748973dc35dce6a99958b8250e1409c95b6884058a357762777d004b7ee42fff5a52b0da7dfd0c6bc12d63dd4af094c2aa6f5ca0164da4b4179f23ed273b4873f58422554d58502574b26f328fcfa6a0e5b88b845d7a0b03e1dbf9d8ba9ecbe2e532762948bbfb7c0ab4dd3b8d7acc5d759bb548fa1aa63a4ad21a8656735354055e67aafb529baeb0bbcc9edf8eda40b28ab8f0bc0b7b2774f203ebbd63d4b3e597c69530fae4f774e93144183fdd51325606fc2cd3ed776d6a19a01168af6712dfdc6355280d02b763edc2ab9126e0b0532fdd8c36b65e042b812139f8e1ea8ad578f5c257a6eea79da36346d8bd93d20d1aa2a2e5d583d237f7ce876d3e0f532ede30665adef6b801fb6370bfd4d497722f3f36cabb338f4cce18d3ec08ec8daea027ba9af0dd220db01556400c65fc7a74efaa5d3f4613de8f6841ea8991aec562556fee2a95e3f93aedd6bcf8f4a37acfe6d1bf1bdb7007a65f3564c82d70c06dd7266f8f4629effc36ce5a8acf4d9c77fbc6d42701b025f4df4085917a37b3504f1820f2707666d2ab1bd7b0def3b3a58d2e9d97ed8878d2ff0859d05445d5a18323cbf61392f9c6abeaf1d636f9390d32537db32b866e791f714876de25ba9ba5ba2f3750d7d63d6ea01da4799ae930e2e31cbb76284dbc29de49cb769f2bc684d85daacedba65a8133f90b693a96ec16a5054bb27669d5d8d3493c59467a010964694f05f8e107b4d23105d8778a4325db8428c21d3448cb7e14b8d89ec4cd64c0140d23e00c97531bf3796d65e76ab7dfec490d5903d2bd6f23ad2a9a098b7bfd004f4ba6ee22e1364d3d67c413a98ab5bf6ce0210382e98e5e991a2d9fdf38eb9dfb201af988aa3ab675662629b591409d60f90d9ac798ca3b7e727abbce11d37e130691ac675551705d2636365b42cb65046341600f255cb452a993c0873d6ed6f1972a352f23b69e7598b8d6e2e8cb2a9f1347e9933cfc56076599d7f246fc4fca80d40c0fcc900a75684e2a5a9c40d13bc812a511ad8f47deda89e239b6c0097e925f0f928c3ed6692b615f7fb6a43b1d2e2ef46cac4daf0b95178333c765d3696c20992df30a3de6a6a35fb0c20a9b4d8e6677e35bf3315be915375755f07ec93eed5adff602c546d96cb2453c318729461b06c3492fa2922ca86ddaa361027a6c76a1b20e6674ef1bf000000ffff8c9dcd8a66b976445fa5a97983b4a5ad9f7e15e3c97537e61a7c3df1c060fcee66e99c34ce082599a386a2ba2aebfbce91f64fc48a4fff6018e2a612c078944af8c9319894e899bef6d8e1898f6ba48925554afc5a7aabf2be89575e462bd9938865cd299d6b7bba14329269aa65e2d83adb303d4c901f98359b98e656bffd649ed118e31a8362e646f0e853a8035f34837e465c42e9b91af367724984583346ed2e4baa6e19e332cbadbd73399cf9d4c88e2bb39352938840a3228675cae3d9b4d9b017ba90c7ddb3ce31c30f19876a8a400aa6727152bab649d4306ababe7f22c5dfdb474ebcaf76fb3324a3c5b03d192b15db53c5689f9bd8a79ba1b30c4f9320b3c3dda9cb229dea86c2e190294be23c436f1474f69d25cc1e5b366e00d80a781879b25ff5dc242bc5718fd098cab60f5e6144efd476ad54ab178f348607c38974df3826b9c9e6f28555659fe3edffb761c87377f513afe3338693a062756fcf542cd6286d5b941b3795ae615b5b4cdab4002021606ee775234dd4d31015830dafe638b9225ae18b46fd1168cf6107a773bd3f8e536c51beac4171699238803daae0ab65ce34a9de84c0614d8232339f6e2cf2026ef6c2e21978e27fd251cdd51c8250d7d52f66a5799e832ccd165e085a0c6ac5f8cafcc3c89ab6285ffa71454ccf2daa3bb4f07779eacb0b60aaa589087964145601ec698a40e6e8ab74bbfd66962193263aedaed6f20b38feb49ff45a5b2d096d19b40fb3445c48dd27c15cf71fc1c4a29a5482a4bdf438ca4a4350ace2b96a515a62c633111380271fb819fff9a3e14b23ea6d3e72d345329d0d3fdaeb34b1523d79556d7da347fb0af1c3ba2a3cce8dcee1768bec32d56b5bf6cc662f7985ae562f51472d4d09d11a2b6193ad32bc9e86343814309dc59c8c659b8a41ffa93af7202fbd0eb792c4eeee4a6d84e5d87a2b983218c7bbc52aca46a4a2c55ee1fc818be7824b96f1ad018d2fe0e003935d32a938e19f96c9f945d7ecbdf04730692f4674eb33a7dc2f81e0d92e6e24d77c3aa92ef58b2eb0a2ee36eb768032b179a11b313eba74e388c5e671d46c93db86edb2cd7a7eae55977279c01d640f67e2e4683a5dc2c4446d6f678502e07fbf562ae7eb1d44e21ac247cae1676479a264e5ccdc489bc39f1997a791779adb12982f5c0192b54120e8493c4029a653c6a9c79556b0fb9e2e641bbdda85be6653af3feb794c5c5676071e5005a26f833e3acee49d8ea226309ccdac86516607f23911ec4b2ef71dfe43faded00bf23e21273b3353eea6eb4e987f3d8989ea219312e326de7c1f801ebb3acd73760b4aababed393c98752e53326e7838b68f8b4db2b8cdf5c004d8ae859cb1f46c85d1735a9553f0a5852f7c6e2e9cf3bd84690e6eb8836be44c441f91260fbb2a1f2fb8ffc7bfd5aa4a2c88bc19d636f114ed5e4df24b48a64c3751994e5dc645b449dfa3d5f29cdb44f0896dc1ac90185895ced5da4605af3fd546abae2d2dabbcaa89939ce5db67c18c32eaa51b6c487b96aba11973d8583d0bd0287df26f56c0398f0fd7500cc34c922ad6793c1a2beda2e9786515c5a122c757168c3e5a31fba09435d11e6555513317182864fcfb272e243e11f6656145fc6797e7f32640ec7481590da42f66abdbf5448a1a38718e6956c4befaf49672ad2ca1120d9699e66f2242677ae04e6fe4756acd526798d72de0f03ae0b795b50004ca4cebf0f54da64160b68e946217dd0a9ce8e474fedc21002810eb3aba24439ad01fdd7176ea473d972f92596fa7de6c1c54355d9bbc91ae840688a5c6b31064e28b198372a545b925a53c93c29d86a579e1a769b26b64bf3ff315f6130664a391358fe84a98ac97582c96a427585a978f142d0a5009880faef80244e4bc1b461be677e491578dfd7d9e8c3454f4aaad9ca046398fce10d3281206f67b8c3a71b1c85fcdce2bf17198a5907c1e3d394c9bfb65dc9961799e0f65e0c15681f6058cecb2ef778e076edc86dbc4e568883980ace29b99890fd7c8c2cc087c06ac26a16774c03ef47b62e33bc78e6e84fb2fa0910bbfb5718cd9df2aaf8201e39ee1ba3b70d6e18b2b6c815b0f3ec6353a9b110ef2db769334a1f83182c26437c412c7dc4f6d4013b3911da779a95631cda206f0fbacdb6d83cfa75d099cb4b6f2324bac397658ec9e25253e7fe8cae6ec317ed5aec42bef0e2fe0b496a46d32ef6c249df8dab5516cd4f461ce9e15cdd4111515b2269e62b6b1ed75ec989e657b04bcddcdfd0a7379ac25ac7a2ffef71322617f7095c8bab75d3beb2aefceebb6b3d790b50fc10712d4fec906fc809ee74f38a583cfd656559525b2172c7b45b71ab1cd5d87e1f9ae1b8789566b796708d6ddb4c94ce7d4d740d8b5a93f6ff087589da58b94a3d76d2695602f7ec25cacf2012ab37a5a1a55487559594bd5e5569c2c9a6fee71995f77f7578ff65d53c423d00da6b5eb8129581917cbd9bcf756ba900bad168639919d4f9f22e6ce65a4e99916e15b7798c4fd8ada298d997889fa13d3ccb50820d1d25daaad3365d275bbd7967172011c5c069ad55605f095bf936c5cf7de0fa714b1b49973341af58d040edd41950c8a63a3d3108f53bf237a3fa369b03de6026fe0174c47b381f658a79bbb8cb0841c918fddacad8f0776a4aac5dbe869c38e58039199bdd71ded998de76e54f79b5afcf640302aa8ab36f730f9103d8e914347e874f4c3a8271d28f03283dbeee66fbb135a670e0ab069bcc3f03d42463d6c1d4337507cd8be6db6585aa944dbd4255a2f1346e4e9cb5702073b4b82557e42d1baed792a133de5869e70eed4e44cb809d4d7da0f76e0c63a5639c7954d66905d8cf489e60585e67c93778cd13c6493de7bc572c792e6093ff7f4859b328e87a77d43a3bbd60e5fdf10004f6ab3f9e575b17f1747d4406d53b5524942552c395d623aafc2e92b90fd05a9c4b6c97ec1e3b0d5f27d0b9918c95862d8c7d28b7e827043b715bcee92f8ea9de1309cfbe25abf4d20ea8e3459c371c6d6ae7103b720472cda45511d9e25f96221f7508c32ca1f69871974373ff779e12daba0f6b18b52259fd5928f027355a717d56c756884f9eead6c1be42d8a700fb97ab2a0d5a8c91b33cc4677e439a6446bcd566c750fc368719cdb998364d916367c529fe9695f67a6dee4c95f70d26e777f6cce31d59ac48ab8702a79331c00d7a869943d69188ea7efadc3a617a4beee483b46f6886af7e76d481deda0805484dacaf47847cc7d3eb4f5ffffeeb445de7d26333f79366b0c82526c2f82aaf2c6c55adb0483b19741d33a5a1973c73c1972c623409c6b253c799a6a0665dd314c36b659bbb5ef9454af40bc8fd2dbfb8bff7cfefb3fcfeff9eff7177ffdc7dffeedaf7ff9cf5f7ffcf6ebaf7fffdb5f7ffef9f77ffcebaf8f3fe6d7dffff1e75ffff5eb8fdffeef07fb7fbfe98fdffe4927df9451764db692a31b439325bcf7d881acd4debb96cb368a89bdc6283ca8376214771e126f933e625b8cb40d12dbd8b59aeb19f5876976ce1ec734808bb05783141eb6a663d2663d0fa58903b3f4a2fb985a6b9d2ef6987bc888aaef3abdfa24f77a1663351d5d9b0e0f093151555ca375f4656781af6f1dd55c5585b9d807f56299b9c37741357b55bf441d3bfdec219a333d4c7b4ebbab3b584dfb0ad799e174d3b40defb3f1cfb257356d0175941b8063b85e71182d90cf99699ead4baa59d11b240fdb2a0051665b7bd347a51293381657d85c1f6769d38126062abf01305e1637f1ae38583e2b846ad72d2ed39632dcc036bb191f4f63b9ec9ce83babd1a4b0f00127b620b2388826b91e1a3a19353966325830c86bf6559bf550b30de3a30da0131a70da578cbd6cb882616ce819f16e78b49e0633ae0cc3d5eb05e88f266636cf012bb0f70d79947ed8b279ac6660e2268bdcbecddf635850414c05315d0fae8a75540d726d2071d06e8fdcd50a0fce4dc3d503a7fb86f1a1f35362fef262f5e75b57eaeb9c1c9f1e8b4959a5efe4b34ed13921adbf553f41c8dcb66e63b75ad6657a28f3d3161314d005c3b89d2fd498755af1c668db72f22046d4627a7e725b35b1bc9024331c9893ea6d431d562da5a795b325914de34854ba46779ed385ff748bdda4c665d43e0c44375af3914ae4a602365b3cf2a066748b5cce4060d25775fe0739d8c41331f8bed4a228f7da073cbe6e2b8b21252871ba65b6b2a6427f19e09b1191f97378c036c14be1c47dc88ace195d64d7d866b4bad77400c46a26dee89c3add839e5271502d26f7b0aaee4858d77fec71e7a810e0fa129c2a48ad10edc98e775b350c83f0095c7317715d5dd3e172f4d6d35af6398bb92edbb0cb8f496151245dc1ebf35927fa41b6d8eeb1c2c8af7f5532fe77d40fedfd32e5f8fa9cb0fe9000906ce8ac390e11524706b8bc14b2702f4c4e6c62e8f42fb83b6da24819969a2e93ab163310117c3f0c3c5f817da6ef9c06d1d8b6c0deab9666db8eac6e23dfbbed35cc11b4a80e4d25797d89f4a27f05bcf81e9b6e9b99efb70b2db4879ea524d416a3516f2bf93b22471398cfbd6b37f058c0a4d388c9384373299fa81c9aa553d431e6377dc08bd18a3e1c49dcd8c6165de886c727742af134cd641fe610aaf37835f4ce9e611adb9a6b976e43b7dd660b8f850048ac351278dbea935bfc4c26169c704d14edd076eeadbbd4dae6fc1c54f6f4279cb6e672387006930666ebe61243824fdfa06dcbd068133484dd825f98ba665797f3114f779b4d94c3531f3eb8da6be9d0e4e0d89b29f8d00a28c40b1e13a03a25222d92d57450bd0bdb6fd59471c55b3bb7e9be349c77f64bd66c43b1e2f27e18156b4fcf633c2c4a7dbc5b519c8857c61f3288cc9816a57b89b02091ba98857cad704bd9463ea5d3c78d0950176a0bd696f93682c98a9c4d56cfbc1d1efe4a6b4320ed16e5b6638b37501f0b519c797af22f644a1a37b437ee7a7dbc27827fe56c84d8399ef27164599694d30f5dcdec86a3de5501f22e9300fe3944f6b9fca790bddfc132ee59258371556bfee85c6d69d7d98fcfdde69cbbac65ed743d4c3edb2ded655fc03ad19d5612e6a1459ad690eda906dee1fbda5e26ec5a2f37efc4ed2ccfe6dcc90c4c35c96789b05d1c03ba7a190ca9b5ad69849d418c25a654a05b7681b10b2ac6b061f3b5adb76ab5299295ef75c63653cf06946a3ca739b6c30d2012361d4a8488a79e054d71de00b0dfcfe2d017e4f0190cfd7e0b71746dfa95f7845a6dc82388502e50ed0cb9b4d3dfb1a1dee8233649e3b25fc5d8a82d2a3364c56ac571c86a87b8105e2e4bb35fe9c9cb0436f850f9522fb2dc9fb6b3afa39d42c8fe80d9db728c4e4d1bd025337f8dcae4a6312a188afbb2d2614001936f5be71b558d51c07ec337d89d28d78bc135b7a51a70a2ebf22a4fc699491eb5107d5ea49c3a7c8e3358b270c2d672684231a2f63439ccb332d085d8a2f3f59d055d7253987faddd663a1d3a928d2d375347630eb4b66f4cc19eb7a427fcc8fe112e13d4f4e2693d0d3673ad3e29c7e9a3bf97f8187be1fa197b86c9560b722c23971727aa816dd75a7a4dd8af7af9ee98da0af6b3ae3189f358eee5a4f177f1215b33c12235fe80743516edacf9d2b84decc8abab6e8fb79e9b38685ffc854930980dd762d64ae2fb54f232e7b493b1ee3dcdab0641761ab136d848a85b100fa11b0bf74c8ddd3b0d760ebdfb93eedce2a6c61826f21f19cb2443a05e471ae607ee48f894aa922c5c6d497af250fd2ec4c669fb88ac8c7c75ff3ce674501be4ff3aaa7977d95bda0c93182c37ef5f2bcb3a12429751b017260eed688fdecd4a824eb8a34e8b2a165d5b2e916bbedb0fd78b47dce69a6c801a532bb120de65e97873f46e3e4bc81bf4bb8e3f6be502c31df44486e0cccf38aadf5ff5109c1d95a45c9e3374a497c4a67920049e1c7c0e344d51a44d52791c2cbe7487392ef970a66b14e3b4429f1d556bd41e54014b1733e37354f5b37cddd515501327b9ee604a06a42efd01c8699ee32212204ac0a7063955fd789d73c7519a6a3800e23c7938500c4ebb817bebcb26fa11fcfde60f3989f5ba54a9407dad5eaa6712a7bfbab34ccd49a9478164bb1a26061a38779ca67569ae4bae44bf2c9fcac8a237382cf2d295df7bab564aaf4b42521efbd4f61cf8ca6fddea3698b8238d5a1be7e7baa9e02c3ba5b419c99ccb181275a93d8fe1bd71bfd81477a79adeb67e0fe6dd09e7635e2df6c77da4da0806d0460e40d954b46abd9f479857884af36562a97149755c863b3de5c4b0309fcaa1d89ce35a19ec39bea9ed560c3c343ebba63fd8c863589cd6899b339006c6a0611a8d8974b17ad4e39c06e963c36222de63c8b8e0c880a7d9c4d7d2910facd3969135a117d864f364ce380fbc96bca573666b6ee4326ee0e1821aeeb997f478e783cc53c21f1fea36297fce1c4a75a6514a2de74957b054dcd3aa363bfc07b27d87cf4cf69e7a46efd177b1b51ff4654bf2b96faf0b9000cbc5052ba9525f848a2db552448e68e50d004e4df380c1aab132bcc8c86fab11c8ca52f67ad0667a8a4d9bdc5269c619476dd2fdcceab09f153862ecdc6136ed22d846735f4dc901986118d77aebf7dd61c128a801fa4246680982045b7dff950cc1ea1e9b565542cd70466e297884ddc6e2b3aea2dd4392636d7bb4bdfb254e9c8029e3af7221a7a6e38dd5d30ec6ce2e75789f5a6f083ab4f62d2d8babb7cf0ea1773d0735509a4dd643dd110f04fc6a9d332983ab433a09edb60513c116de42669a2617ebc84ec5611fa1820a893a6da94d502621f3c6164c983d36b19abd0e53405fb7a1f77eb1b2cba9ce3b213a45a1096decd4610113d26e64cb9394b3659d4ab8e7702512a4a7354ce5b517a7a6390174ecf7f63e04aa58a5b0630e0bfaead9daf21fa2cfbd1dbccbfacdbc6558e3e6527f7f72c2da3208e4ca2817227fad536f9f8aabb5da1f915ca0a1c51ce74eda0f7148119eb1796067690902ad1af965f41ec383dcfa6a167ee1d3d2675252b6b17f61ae4ce56d62fccc35dce63977dfed274a2de839e3b315fa410ab37f3199f8c637ad9fe2bcf86a82dda0b7bc79ccb6a6b2194595c548cf57a9ee10cf368a66ec75d42361d3a2b2866281d0632a3c1f0b098070a94300120cb76946672fe4f2a1181e5cd78a5af483517eb7cdc7adfaf71dc323901863192b7a22efb6a2f17425185cbe179a561e7955329269d942450e3b61a9aadc6fee651d6419739bac77c1b357377ee97de95c3936377375bc0c112cce4244c0ad94480a19335133318ce52a839c5b6512d7997dd456535788e47c4d974fb18a1863593e4838ae096f8d85a3b2f7012d22efe640c8adf6304879ae62e4a41e4a31ec0d46be99fc629b911eea7e352310d698e11db775951f61cce625bbce213a27ba5dceb753ef8064c26c77f8eeb69950fc457e8fc806f1d93077800dada2cb0976c4c4fc273f5b29866bd4b064563b7673e1a31e46aecf8b0a2d6a2f4d493423faf220bd8ec3c9d09d33c265bbb3178b466184da8b23d7c1714f0ded00a3b542be04f6f86b549fd2add12e5b82a90cc79a1325a6f6c917ef0923ee185dde835ee72a46296acc5c4c6c3ea31faa910dad9befaec85f53d313a0e95e743c83c86599dc3d66cca1e4854aaa7b33fb21f1cfd6eba2a0d3711c0c5b0717b11132cc041301d85ee37bb179853833550dcc3e72ede695e0283d75fb8672c43755d4a2c64a2ca3da758ae871dadd0bf2b35f92a27b9f5a11f4bdc1a0684bc1aacd07eec9b0d5aa2ddadaa63b24821f675ca2838bca3189230ecdd40c828b94195336212a06f6a7d33129240178dbbe70cc24e3e2f006b5a4a2189b4e3d03ff80a7acfe8ae32bfcc9cafe71216c4b7bc38a6176216231eaa8c6a2a47f31b7e2c294af74e0ac1b6d903c1d9cf275dc10c796d5946c0d4cf7805afc5b01f15b2b108eaa2b83863960fd44fb77025b5db2d54975f6eccb58611f2dc496e569ba9d28d66298575ee828970c8bd59696cd54b76eddc6d7d3bc189b2ce72ce997a5b276e99891bbc2c8182cabeca992e3a52890ab08a7edec637a043435a68de9ae668033165ccee1ba4bd7ea61cfdb44a0eef27981ff18124ad7c7b63762ec742eca9648557a65e53447cc98a5ba9badec6d51f2dc97305c4dbd709bb7f692db30e815196dd5e7bed7dd8b3bead04d5dba6844f26adac26f3fab0e566727ac53f7726f7da1eb177c9945a6a3d7d99acbb99ef2acf5e1f8537e59f516d62b7e28af99cfea775b67567d114802b7b53a3a3b7c9426ce9845dda6600cf8112c1ca54fe3cc055d65d32f6135b2a5b5b3d66dff73fc06b5b64e37c404fa748a408bb5f09c00600d3a7f312bddbd46b899336dbc793060d084cd98beba6208ef36e98e07dd3daff0f76cbd3a59bee8434040ac7c5888efe15b78bf1d9b7ad0922466a5e33459b84ac7deecae15864fb545d307e8d952bfa17c4ead135dcff56c934f9c94b6e2731553dd423c3473fd60bf6d36f105ccd9882237df2fdaed36ccde342a2db35a568e26c6699010228bc769a828e04dd76bd69724e8896dd75ae0313205224a3b1df1e491c29a090599b519dd931ec6b2ea3b0a3c9d7d441f66e5ad230e73deb05f9778c1be86c5c6c6a8b1d38f7a408636bf9fa34363fc81e5211b660e6d98132c890928e7e7bcee977abc3f27583ddfc1dabb5b4c3b95130e63ed4459f8d9e3958388c5eadaaaa18231a68fed3398f67c5ee41237fb0c6637aa1599bea9aec6801d55e5a6887542b72d927e5052fafb7156eac63b61d4b37e3002ec8c280ca50442cde18c3d060c5525a83db7bb7e2ed953fb5372e1a64984af52af8e75abd8f888a64d03ca0fc12b3955bf97dc0035ad9f43af9f2b24766d5efa35fe36c326f28e584a206612e3e4f7933db8fc16e863d867865a01a8746acfb1782b6cde434a89e9f9c2f659a51f62badd378b29d2f8c13c9df94331c83d7e8818a6295d10b43c0db0cca2c72ae087d0679c9754a31a83edab69bda9267bf46e1af2590c3a5b6b21ecd19afac13cd356d240162f8cfa2511341f8cb831746cc70a752f13241253bce5571b74024bc61a6c86b4fe3cd12d2ab9c896c0a253b58f60eeed6b30c5eeb3726ad12e51a4bdd7623dad2d615e750343362340926d59edca2ac65b3aa4be39b4e738e20a1b9c55c620cb53be379c0a43da0094437a68ff644695dab8906fe5974963cfe6ae0a06371e53712ddb0fa3ba99d4f7ba6666e742aea3359ba8fdedf2e2e69a3935ef0d628735cce6e67dca280a7a054e4c803d4a12244456cde899173871529759b26f04b4198384ce6699c373f5be9bc78cb7cf8ddefb77cdda4c82d2cb5a5bc53d27dbb65535a4c660013ebd296bd17d4e34a7631e291a9b2f729922bab40002a72ab61beb2ea7cd135cac3e4632d19b2e21515d398f65e1ca575cd9aec0e1d4cdca6ed3d97206160a00292e60684f04bb6e8619f5596c0f62510b22460b6616bce34e4ae75945c74c601abd69d3957a829fc3842caca6a7a2a47344d7b5bdd7702f076f372751ed06d3532f33c86e1e9f7d8241fd92dd238b5b3cdba4cc76b60fa327ab09c65ec58340518d6c7581e31b744051e7d09d66ff594d9332ca0998b795fe41e7f5627a5a064daa08c065a05e6122164bb1c9f5ce6d326d4631bee2e52e4c79177277b6b1d3d14fd368c42c7815bbdc706a795b695dddf332905c6f9e0c66191613d7309058961a6a1c0ff66d9bd241c36c984698b5bb748c354593821afbf3f071f4f979ed5c6d23b379fe412b1ef5cc78c2afbc8145d0c00d28abab6d1eb36cdda9a3ac6f2b86edb2b0364f7712b46386d27aa78d3092d1a1ea5a9214aed0991efc2215e69b5b352274f63c13c8a9350abee7c52351188937bb9e20ddfa4c1dad92cf4fe8dcab359daca5e5e7d2dbf42b834fc550227ca2a016b6a20ad7989d07d4c175776db608cff405f4029a36be37ae11c134cc79929443e98e50ac3ad5bec24104b74d697a37653bfc9071c982d967fd685f2c0a8ff98de4e2f7ebddf2ca09a39a412016129b614bd5646d162ea9c58f2058e2be5b6bf647f04f1bcb6517d0dd86f596651314abfdcbc0aae8d4c82f606a5f603aea221f49537056a0dfc81f11706c05f8fa1a66195b193847288e492fbf693ddf3822e69a5671243354dddb428f36e6d00323c8ef641d984e88da5292e48865919f257bdf1a54487ce230bcd671ab0b5fff7f010000ffff8c9ddb8a1c471044dff515cbbe0f546565ddf42fc2acd1228c75c35a1b83d1bf9ba86e8195a7dab34f0621afa499eeba64449c58892cefe830118c6cd30bb2e7398aa1e72c2d2bc5e330df92371f86ee8bde7c84af57d9e3d42258909a60aeab4c95e996aa3285f815eaac11776f9d257ba4fec33ebd858cee8253c71da5e9d01a0563d1cd12b45d978ee238beb4416f6c2aab72b5dc5b5aafc9004a0ea7a8d72a5d599169bed097957e85c54de0b0e8a2cd535e6264ba7bcd8ea8ef1654ba957bb34848161d280aeb0352b7f3e5ca29cba75d43f1565015ac63476d913b214343a915605a6f82db1040177d06a719aac90b6caf5b6264e2cca8ee71991b71ec74e91630e81394791a1b84d8c521797941623fd71052df293f29ae155ffefdad44f380d9234924a9aa74b2b418d9f1ae5712327b2da3424a345f4a5effff24dd31b8d01c1991a8d63204026dd4d5f9e5f4993150dc2d150274372593f09ecd36900600bbf27c24f3ca8b60652eb2054518acd4221451c9c8d741e35b61681bac711d95754bb9ca41cc4e9396ad35b494a9ae8db7a37811bb5d7e6c885b5ebdf142a63a4e5d425c5718c95215d77c42b53b40ee9408b3d900535b53fe89e2472d0278ccf31a0e45f574c8b18cb3a3e44463b96af16862926d3f17987847ee15466ee566c6003843b16599d7e3ab2ac63682bb5536d8d80ca2eb61b43230b77b1e6a87129bf5de44fd1887d7661d92cd862ea76349c601a43685d599d60c72f7818dafb5935e8d09dbe9b1d0f3c16aeaa4b49cf147d462ac068deea4f3ced0bdc6716711cc30568eca905132da60b457cd7c8f9c77d6530fef164f6722734592f1143d3f0e56b788e62d6d36775dffc34546945707ce4f0388ca0dc6ac45f2d30eff9ac5822418260b148632db92e47e296490e799f86d45dcd979bd92c31619a1eda04f103974f7c99768252e97d2ab0153d046302b468255ed1ce153105c79f638a954c0d5e00556477778b0a44b614533f9416a82c56294823994d75e1c97e45d4e4be397f9b39a7926876db20c50c7c34e2c58b552086d9fca95c1b8363abb1292764d507298263c9f257d8cb1387be49e09937385270131c7f27519d15353619bb1d04bd587863ebde52acc3dba2ab4e1f4897ff168ea63eb772651b7abf6029bae7535866fa2547ab27c4b2a1128276f0ff2bf2a0e89a045a54f7342755fa4e9de2e87f9f80117f4a4bd9e4a7df11023c6c45f5f15bb954307b9217cb2186948ea057ebbb41a1179024db61e95d32eeb1c52713b4ab43abb1a4a1536e7d0c31c350d4841a9a9f1209cd62b8e853a8d82f3549628c4c3e6aa70e23454d3c828eea7ea137db2a9f422281006872a6a8b672c751fe428590863658c67ea7b81a15a87f316fd2f0c0f9d6ab0d08c98bca8350a74148467ae00de527d9ba58cf6b7617960c81b23033fecb54d36c8305051c316f6b0622ec40debc41d761bcd7a138d4849f08c1953ea69cc387e4eb58bc785354a668cc93b91d2dcc85e48808f9bdb0516698bea601ef507a46721e15f71a5d27d6803724ccd1956599feecc81fe53741ea31cda6bc7aeaf5eb109248d4428154545676104361d37f9b1823471591ee6b8e81dd60d94d8a96b15f345358f7a5ced15f464e448358b16140eaf5e11256b0b2442e088c04ab85e6fc04869ae4eef4da363438c5a892b68d262639419cde672750337a85908c693827ec1e6b607c9ec20d92eb631f1af32397738d7f60285788ff16252cc604910b3268d34197e918913dc74f56c6524654777d638b7816b94daee1ba144a58a90e1ed4e56f6ec5df19f33a1fb1aa1637ff792e1f251a90d6cb7e68a86c7a56b0b1adf0680707e3fab89c19fcb53120336100daea24ae122258dd7f8526c0a658dd1649d2603239653b9eeeaab8e278a64ce3c230c6556d938c352947b931f3a7e071a72e34552ca2f47537951772526e54548bc38e35aea8917d8910eab3be4d62a1134f601c6d691db0eb473dd72a57ed9cc3ec456d569d621cc0ad90ea2817c191e8e18ae210cf80bd98ac2d871d4075acc31aa50af79d426e4129e71f27cc8de98fea9a90cd7a0b65a6711a1d46126dfd166aec8509a4f2716c2ac9a42b4d369661b81575b97b5125fca98e3366a35321da20c77a22e0cf8205b2757fc45bdf512477ec9652bed5ca14aac6015675d219f1a4f4cc68730a95aca6992bcc8216b0e174f9e241e9f627a6db17d64072c12ae75d466f74c93572ee27da4a794240b22e2d9baaec5d6aeada1742b50ac501981b3fb66a9cd4c79d99d13aeab4587fc4e3a96344f8f9fb66aa90b1d9b635171e2bebc0a8be20ae332fc97e8c2744a812d4bf725bf6139d5221a2446516efb27f11c1a78298d24d858e8b0a5fd1c11d6663f43efcf57d12689af2225748c792f2ab6e4674d0de52222b720a2d79bf8dbc8f4e4e6b831316872c2905aeb85d7a0da582363cacd2113aeeea88a8bd436ed939ae94df5574dbdcec0e920d4a9598a3678f56fb5ce6c8f1049500e54f181f1ba3c6f93ed144d19bee8678868ac95bb6d3dcebcac4aaf6577d0e277807422ccee8c66f9a264f97d3c9e4e64738c0110c1cef5681aa163e03f6c54962aeee9b04d66d70e78954e0d70c6691f84bd794d31225e8d3ea9538a1079df11d8645f9684cf8dffb4cd050a8c9f7799954def7ad927beb2934e6ab861ce78b45e0df28ee8d7bae5164c554dbb512cf9dc81518ad636b207f520ce8e9faa792258891a928e0972abba303621004ddaa2b94d8fa8c5864e9b231b8b06b554c49b938a5eac3b1adcd5328a4e9a36d4591513ae7da64a8c4ae0195d3263b28427dafbd42d34b0d18ad557a385553f13ccb58d2a765073fab44918c1f49f0595233191c648e515b00c519c3377c45254230e029f4e66586e85d6f08211860acf0be25c9e46ec8ad3c1c204fa8e7f09aba2e28695559d26358e2b8b880a688817e1d4b05cf7e9a863d22a5835a40e8f825e0683af6ed7f963c32b5e86d4ca748a9e554b1b6366f2b5a1a460cbde278f659fed3baf23da7fe3e148cd82f1062aff194c0b900d4f414562b5957b8be0d1fda4400bee8a4db22bdc903d8fca84c32ecea9c4a1e64403c028996e2310d2478976b97da3a8b6b888143789429d1dde7991e66682b945ca463ce5fa482844b4654f88eb70190bce1ef1314e3250167a25d6a52866371d22982c47dd12fa3a6a33ece870c19d9e14a175222a73178a234affe0bcd41efd2fb0bf6fedd9c7f5cb4b4685c99888e92e2267ecfddbb3a1b306030573ba0876dc8ef94e744d575c1c737859cf62d9ae9669481f0add2596c66b7ec863525fa539f79ef7332a348df711a5aa7a0cadeac88ed56801d1c6a62750fd04862f968c316518443988e4b6cd9699f5581562d272c1c05babbc67706c5d1e8438f2b0d55f059ee9cc08342e7570b6584624d9c41da2a31a56201b6f69a2d2eb46b668eed0b5c5583659d4ed45fbfcfa7e3107568cc361625c273b1cb94785813b7b895345e2c38f2f5c1c906468b2d5281ecaebae443209485c1dc45a7c8c97eab50debda35cf5f7cb7fefbfdcdc3c33bfdbec74f5fde3f7f7c7cfbf0f8f2fcf7cbedf9d3afcfefdffff6f9c3ede9fdd32d25bbfd658febf7fdf9ede9c3f3e3db877fd6fffff8f58f2f9fbebefcf2f2e5f7e7cfdf1edf3ed474fca98f2f5f5e9e3efef7d7dfe84ffbfee65f000000ffff030052e428a68d8b0200" + ], + "rawHeaders": { + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-Request-ID", + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "913d13277df983f7-LAX", + "connection": "close", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Tue, 18 Feb 2025 09:43:47 GMT", + "openai-model": "text-embedding-ada-002-v2", + "openai-organization": "anabasis-llc", + "openai-processing-ms": "73", + "openai-version": "2020-10-01", + "server": "cloudflare", + "set-cookie": "_cfuvid=QLeXQjWMwezdD3DigO1GSfW_O.Zno5q6n.y4RYP9N.I-1739871827705-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "transfer-encoding": "chunked", + "via": "envoy-router-65bc697f7b-cdv6n", + "x-content-type-options": "nosniff", + "x-envoy-upstream-service-time": "37", + "x-ratelimit-limit-requests": "10000", + "x-ratelimit-limit-tokens": "5000000", + "x-ratelimit-remaining-requests": "9999", + "x-ratelimit-remaining-tokens": "4999953", + "x-ratelimit-reset-requests": "6ms", + "x-ratelimit-reset-tokens": "0s", + "x-request-id": "req_bf7743cde36400ada130b0108d7d4607" + }, + "responseIsBinary": false + }, + { + "scope": "https://api.openai.com:443", + "method": "POST", + "path": "/v1/embeddings", + "body": { + "model": "text-embedding-ada-002", + "input": "testFunction" + }, + "status": 200, + "response": [ + "1f8b08000000000000038c56bd8e9c470cebfd148bab9d401245fdf85552d9b943e020718a5c61c02f1f7c67c4c1c769b2c516b3da194aa4287d7bf7783cfdf5e9f7975f5f9f3e3c9efef8fcf7ebd3fbebecf9e3ebc7a70f8f5fde3d1e8fc7b7b7ef5be4cb9f9f5e9e9f3f7ff9ed2dfcedc7cf5f9e5fbe3e7d78d88f93ff82febde9fafc643f9b3933018ff73f8eafd3d8eeedaaf7f7608c3187f7d881a3d1f7d0f008b0710bf570f322eea1be9b49c85b1e9bb99df7b7b213d59b12cb658c8fc696474e6a663e917e07666e45b64bba11d1dd21782d73ccd3256323bd3a70bf19ecc97541965ca320f3cc9c592d79392bd2a9d0baadcaeeb4b971ad4df1d258e9770cee9d880c61adda37fcb82011d803437199ed424672c2528837744c1e492c8cd8be57ccc2670f8a27b7e61049d3634bb2f0b4f45857414d67adb0e60e566b19c203aef9460dbcd892598f25f5ad4666ac08d2761a33aab24a18e2d05e760753e106667b5a2bb915362a75a333bca2eec798ccab63b50b73ac6de5d48a588acc76a75ab467bbb6e6abd5c1c4fc3f2ea372c743331b4399b4cad5947d10ec57638e6a616b36e5ffa4c18eda0e26b1502d94d3287e016ccfa8e5153766d52dbc3aa5f9cac373e2f095e475b5d86b5bc788eada8de051415f98589ba755407cd41d6ddd540639a1ce987e6570c71fc928ded51d33e9544f34740d67b554e0ce867a9d4d6d406b9d3d0bae6430c49afa5f5bb80555d9065ba8303045ee5daf48e6a8d89297809456d6d5b7222ce39823c5fdb00e33ed828bc02933455b016c0835c904d6b460dee5702b1d66e82c1d038e652d9589ee6450b1a1c35d89e8e690221024e8514afc627c5321143ab3e7b06011e3f71b921b390aeccdb1f5066358c661ed8e99129baa287adbb14138bd2a0f13ae6ad78eec68948ece8b8c18d461769d6dbd778aa22de6a80ea3d295e3d32bbeef0a613de75250c41ca3d389c8d358cb31dab06166d65a5fcb61798434bc338662f8852c944cf481e731b3702d2652da60af53ddc60a086dc04b73bda193dba6aff5419dd998abb3cdec227764fd886e2e4ccc6d7b1b485954ae2579743274d5200ebbd80643d7b53783dca3230d36237535b42e2488c1f9922dfb6a078a5f665a79aa675f29a83210b575b8282fc5a9e4629dd60ab6b7a295dabd7470ac80b9e5844cb70c8bd576b6c8f8070000ffff8c5b496ec3480c7cd100dc97ff7f6c403973709506c935481ca99b2cd642abb8e37c0e7517b884929b19c8d46a4b95c67354c914bd6c8f161eb88eb7b80f4f78f13144890da57728d59026b88d9bfd86d432aabdb1f355a213e79e5bb4127acda8214d33cb089cfc16b9350933478f0f2455871d00b7e121c8bd80e1d998e4aa0c62f0887d93d8a7c44f81215338e4709481c7e45db1f3fa9022505dca156838e0d4ff60f8d85a6550e97b5706205d4f0a369f765a8b02d8bf0d1137cd297c83b1193c59ab892f69fafca6e721120e7edddd4e92127b6590882893c61c3c53c3e0b4bd7d03c5f5c929fcc8ab2c219292ed23fb7da65587ea38c5375601a2d453be0ef487239509bcbddd6fe6f75fc74d1804a256b5c67f2eeb6e38a26ccc5c1dea54ab7797cacc4426f199b2bb9346fa8c07b5abd7097a259aea16515813e39ed950d3b3aa42b34f5af1a2c4d3642a08a137b250b48af9a3e9f1cda4a448c23c96c92eba49395b4b0cef70c8816dcb6eaf015d52891b6bc51451e9ca4f722a42a974470491df1b1c22d8857e225b04a15b2b8e3315fd72bbc6f74fd352a59b0ea7b64a51ff5b98bfb84953bddaa4a7f208033cb19deef3fd13ebcb8cf16820684704920c221985c1aa6db94bf720a7a7330a6fadf5e82fbe98cecd1092f916052af748cb18ca85ebd46dfea9d5a6c03b589e7244f19dadac7822cc978ca4d6dce8df07bb5aab4ba1cf77a6133c93b8d6150232d4ce2e14e41a1aae4b2a4a524d12cda5fb90c6f17727605f56d6634a4a067226cdd62ce0b86ae5d964348847b4938692cda0999ae23a1a343ed202efa06c8cea451f86896fe0be577450c6b6ab8376e20d10c4c42ac5d927d6bee299cc1f56bfd8e8730771470b30176369f8011e63ab4170a22e097d68263e4da07ad752d964436bec04b99c39950869bbdee80ba59b38bc96ef9cc50eca41cfce34327fcfde4734cc8d4ae40a63e141ac20656bf1b02c5cb3e903ba3c5192a9adcf8b2ab469c38822bab58901ba46173e6a2be7005ad5aec88f5d6da5919b48cc494d30862ceca60d0064ce852fa076778ea22739d2c2f6cf6eaa0edb5d475c96ecae634864c4bee02b1dcc53c5bddd0973ee1c8f91a6abad4b74b063d2d60cc79cee452c589a1675b638ca528daa106899cc116a8dcaee15ea231db741cbee2076866d9bb20ca10860a70ce6dcf3c16b0a26ef83073e81d2b4cacc09f9ce0b30741ec7b267f9195c3060319f54724308509f632c3d464e46ad5715ce3aab340ba21695e18a88e09bbd43e19e77b561bc38a1a6e871fea8d02277503a31c3d0c838bb8df4b990feb08b050219f053cd0b8738dedd410683b72e09793bf3099be4321bf46e358f9fd154eb8b0b3169f389f1f441213e73161abe57eab423d2f9ae537dbdeb10b19be2d37039a9ba94fc4a9d71fb07b34f53cd93cae01cf058d426a593184c4a680af2438db9010d7f1f6e524a8ed025cf4939dd15c7925936ed653805a42a32c3a9927ccdd056aaca4c02e1cc3ee586f16c0cdd6ef6951da500a0ce3f751075ff0d4d83b0198476c919cefed4a352d0c8cdaa14f4c92f1518c2ebd372652b3008a42e3e5c679273b6cc221b919c6ae6649d9c731fa590167c8e637a91442a6a47599abb9edb4d826e2fbb2d98d3e6ba50e6efe07c1160299a7e2e17fbe2bd6b96a0f97b245cd391eca956c4a03ae1d7fde8805e276f64be4be4bfa82965fa0f668f6a464591c453cd1e6133f07c3c5c9008bf6d0ad4dae7dabc68e203219d30a0f7d3ee83f43e8a320fbd7d07e542b8ec1cfefe4e3648cb48957741534bcd6dbf10e45a3d0605b5a5fa62476494245210667cef9b099f7060a2111c8fc02898817ad14493c974008075f4062b475e64ecc5363dcca53a902d57942d2956b290da68a8c3f34b7a2d31073dc639e0e3d871894cdcde3939d73072eeba57622822de996ef44a30ceff705ed54c5c5eb820a7963c5677a32c4c0e15d1a4cd556ff434425cbe6feb23526d8ccc1acfb3ca07ad57bf3cd15f52be0de6cc6e39b0bf61bd97b010ef8f89a006edb5361639611de01558ddfe877fffafcc4793817a3fbfaa03e38edd16125975c0451b3ee72019057aa5ed43d6d2058a2b08df35b28311b74a8e57237eaa3e4b00b46e723b6ad08bb7eea56c2ebd2c51686f4c5118be7ae1136a42ed19d85510bfa1b088dff4a9af21ca07bc6702932fb98daa1cfb3502f9e7ff9281ca41622de1153accf7bfb7483e966c3d2608aa8838bb0021ee22cd361448321b6586245aebfa66e923de96eea46bb788b44bd5a9736212a13a86cef03cb3b47f11f70fe92f0b24495a3e938eeb25394e57c6c1d94fe3f445ae940e8f2999b2ea32994624abb20dc0fe20b8c6919bce4856a36de1c7151737514c7c0fc4fea060cf2b45f1a7b92344f23a1fb8c7023db0252721679682600d9fa52695e9194a35af9f17c3fcb12db27dcea28348c5acad63691de2d6a7c81ed1495e02bc900d5948bbd9d0ee945d4a2798b492aff0b1d53b168af8c28d509c2abc7df2c98571e3f7e31bd9bd022aa9bc052ea41171739401695a1647bbab47d3ce64e69e8680d99a5e8e5e4369dc9e2bf68cacaf932fae71321717bd2a929c79cdf4584cb4e2a214dc9694cac2b05bf316e3166feb641f76cb644914a633d3b0036a993e490db029434ebd699faf82277551142d609e57542888f6b4131ec9db3c52b18ea02d239d92eca2e3b3203b8058ff73d1d6835d49a8ff61bcd6d9bf39b43f85ea96e890b8d4b3ed4d4987305cdefa226ff74eea8538f4d31134596eaf8d6db52b20b55d5cfd5bdb2e140d9d998a766c84c704a648b70ded6864e62d23535feead25d0243c3f034d29ddd50a40712fef9705848faf878bcc07f9bd84227e5f28081269f7b8bc45d1b3c45fb2525f16165ed7dd75ee6b061c0b7b1d63a2d0e7f62d83bee820861e8956df0a3cad231c982729f638671c0ba435b32808e95b48605f6ae22622a0fc993a6000f9e15b922cf60830736f8f65827611a7778a52afd8e818e8113f3f79719fdc2eb24924a296b77642416d5fe1e1ee846556fd84aaff020000ffff8c5ddbaa1d47927df75708bd0b32ae19e17f3183078b669876bb9956370343fffbb0aa4ae0132bb7ce79321ce47da95d1519b16ef1e703651fc6a76515253d71e66d68000810d69e2ae105b196f35095b5d29610060f45e1189530f0a775bc8733ddcf89a9dae8f9ceb276f3d8a4645bb523bd46a1c00f01bd36597906d3761f803b581a2382ab3bdb3d1c8ba410aebe98450228329d302db3e89cb5aea231b106dd0ab81be758060d8d92daa94cc3c88fd1bb7cb085d6164e2400e802db938a019ab0088fe5b9eaba95da807e4de545abf30b486b4df0c66c552a3de5674c1a90e79e640efe06a5f4d475362892d9a277abcf663252c2c9f580a334743ed3ab75a586b0ab253ce9e3c64a9b988a6b5b2fba628e67cfe95557503fb313d387d268b737d4fc5cde3be1c49925002c7b530385ae24e863105671dffd55b8a9c605063a353b7b3711df9b590e95290405a3efe4ff7290b442b3fb85a44db557fa76126698449358527243c7d9545d31ba9232ddc2758f6ec32ef282508970e0acd41aaf82656320cd206ebdb98f82ea22de13d25faf802997e643d43c42c2126cf554deb6d3784a46aaeb793240cff4cb2c7435537253d0b6d0250cafcad9c6adc8aa5e329fc995db4ce68406730cdd1df08de52aea4bb605cf93907b93266ca5c9b4384225a6a497f72e061869707dc481963947c4e556b58bd8a0465333470fd08bae4d7cffee88bdde23dc9e1b010409f91ef72e691a3ecd6599100d00ea93a62282ec6f199de5b6a9c3568877aa08ad0dcd9c032c8822dd34045ad9ae9caa648f34d6480003208ac0355336a9c0b294d4932f54d801b50c8d41997ee09a7627ec43b3e3c1c93e2f6fe6864190deccd6e8575e0cc2605f719f4cfdc932b2e7c0e8dc96f34cc42945ec2dbdc02bba8d91e1c7c582a3a8de33793ea0b7cad4692aee469ad8b4376af040a336748f64b9d1b0cd5a0e2b08a9e871400d497f4fd1f8dab0bb4cc053506712daa1132ddd85a16d6a16f41ab4f5c7ba58d47b87eb6ecefe17c830e165c888c93424bea131a1710d140eb5974014e767da59bd46e70f97fa61c687cdd694059db9744af4d16050b1c43021dcf54237f2c63bfab83aaa9c4825f8963675c81b0310690c2b806eb346db4ad2674ddb2e32f10f4b5f66536e2b10e6323f59e94ef0b6b6edc8896e9baa2bab960b36a1a99f84661a44f5bc92554233fecaac9de39b01f7ef2968336de868e7ada4eab8c4a3809e0918c66bbe3ca01b0f26f216e1bff54ea2eaf340f62d2489f1bd2a9cf537e266f16364f50144642721f100e136776aa0955863d2382d48f27b990ba88d85636113ff8f0360e71cb00139d3781d0221c8c43731c7d253be212322eb289c09cc69377c08cb3fd2c48942203cb5eb6b7b502e8744a1b59d3f59c476d640401b351a7c1183988cda73c9543a47a503fc01090ba0e95bf511d46d65484912afc475f92c70dd0145dd3c9bd8d0a2abaedb63be4f295410f3684b2859e66b1e2637041f5812c6b1bb27f28e32890661c67c0c92fcf12ef99e641dfc4446be2d03164a8620c419c4d46d1fbd432eaba92b960c548309a57af772befe70452c921dada61b5b61fe4bc2e5e0dcf1cd3ceaf68c64254e2162831427b08df3d8e0283554f96c3928e4d1be9ec4830abac28734626d5c47627b3604ddc626d158b37a07925d84ae0eeca14562982c28386bb689970a8d6c903026e77bb29547ab059885e40a1dbee832a2199349b9acb4c4f7ddef4d2d0fbc70450b901f7487fa7ce5e84cefd9199346f8f641171a20794732fa580cc15dd1e1329595b7f4a4baa920c1d04afdf2f924c4dd00786d763fc3bff474ece5a44f05c7a5d9d4ab8ec89917e8c2d18d21915b36b164a461bfa150484928dca663ab3be574980939f7257c790ef78b023c9f8aa01bf521a83df65a1c44a14d923d036241ba7ad0a9b96c82d72789c88a0d488ba6fd02ad3a5b01720d3c3a3868af278820702d50c70e96abd67ec7bb7137e1051fd7072cc5ea55420cfe410788f011af384c8dbbe9e1c24b38c39a60be2601be4a548cc4b6d25b3a8346572067c1564fbf742af42c79c7ac490205d61bcddad3a3446b4c227f399cb461acf94dd49a49f07bc5e681c8c1d0d67b98d20f38eef5d69e747f37d1ac49902a348d84791a08fe99ab06b8625345811fd197d1645eb63ce97e80be794607418b3531c7b5a14f263dc2ba0481f911f9b6e32a0847bb1c34722fa8b2b3b754103d128beda2ed4d06729712bab852ab9a0e54a84d60cca09f47dfd2d7cfbb9d72bb2016597beacc708b988d665e910da5a4f50e838f84c19093af4c109934f564abf9e739131cd2d6697b3e0f1abaa655029393ef547e007bd59eb2a36a5d240348d830e987d0bc2426f3671708a8c7f5ca929df4ab7b28fae4d990339ea188c18969eb387609ac3abaa7b7e25f0016e31975026f234172d03b70bc18aea04f9bc551238c082852fb372a8a10308b536f8eb4de1e3209ea0d7d0b33f2477c068453cae124129977200a653b7128da38b6e6686f125953f9474140dfe37dd65474032057a1c338246bcac7bd140a15c224bb8b693b18a4a7ec269621d16c8a567425a55aa2a14b6a9d4e9adda3d2421d24f21c722147303e6846bed95d71e47a84c647c507c8693ab9463a27d572b6651e0213073dfe84caf49e2527ea6d12d477d389cc6b856a9c04fc5d55d3f77c60a5ac8b93d7a4021f2228524e8b4f8f809d6d7f20f5c840676e161ea30c1581f61d88431a0de84ceab8ef0d319bcaed8916dfa94fb28538070891de4ad5eeaf858f4a9e9593f9492d4527082d905824292788e579899d0a6a914de29224cb4f1867b6cfae1ac658fc3aa3ea6bb63bc120dad19431754ce52311ed2db64189ae9936b445b8552752fc65aacf09c7516f27f317ce269d2054b4d4a227060c3759eb043fb7ceb406731140bf244dba12d4a815f54d89136e958b7ae1e50821e88f05c07a4235d9a40faf68b6239d02f624606120a67e67090790011637d24ca126a755cfd85d41a19cb19f534370378df0e7914ba7f1ec92f70730d974bb5dfac4c9b50724bb4d703d74f2ca4e904344200ea5c5b788b90066a3e8d34addc2c11101bfea346328a0615694c97264bd4ea13434d5e4233b0fdbccd93f6c261c0e2472daa4ab5fb8104e7014627083fccf1c76fcc0825b887654dd4ef90d20e2d288f37718ec85425b22906f366f1d1ce754b3dd4473e6a8aa646f920c51f2cefdb849277909d834f5e5982dfaf0ea688be9301c25f38ed26d7f03169eb5fc4f64872647510157147dcff67f7f31689692fa0cc49951c088c33936d95f0d4590045506d876672dacc26d3a512a64d110849916f0a712b9068de584887958fff2d83f34e92514c97ce1b368e1e38a115f716645b59dc35f0291569cf489a3d6671aa333250a718c2daebcaae88fe7e3e33b40504d27a5a6f1edb02c93e21210ddb88d687f6f04f6e8fe0017b360fae1b4184c9fac26f48ea973026adc64c8478098cf7c38441a50b6a064bd158abd4c8680ee8ae427ec7e7e0283d06d24030bb87ba7bfe86079e0f8cda77ee03925ec097dd00c0b46a5021ef35ed3f6a86520a113f23f83de60cfa466908e0864eb282b068bffcc0ca278c447d7b7c117335e32f2da8e7133776f82e82a52b09e72abb4e01c71ce52bc42f26663009b531895bb2bf7919e6750cb248776338d5c9c7eb2370b9cb49175353f85a1e87692e52f0382a860c330acc41301c38687e9cc85d16242f3486e2d8ee42363cb7de381e1a470c58d9b977a96b2862afa23d990bb61bc9e913550494dd604cb2c26cc028d67effe50c56eeb68328f42c2ee33ab50ae7b97ee1ba0b9539356e8a18c7329dcd6a2dbf12a4cf24e02c4d1e2f1a49bf55ee478637fe033349691eb322b36ddb6d6003149998fc02ccec32da52078185de821834add9cf0086b040a4e582bc172532f2b15e4b04cd93501b80b254aa25b61419e0c9788da26b90be80a0cf9646402d836ede514defb74450dac840c56d2ad84e3828c12b23862b4da1ce6ba5ad965671546cf3d8620204673b7cb24005e87dbecb2e4602b84077892c459e0539b22bc1915fce89e527b924102e97bb1eaa71067d6342abbd4142d53fafff7c478f062641746b602bdec3999e0c8e11de94681234dd97901c7c08c616467ce2315eb5912000a282d7b8111621acca70ee456bf714e005aa45cd45e9f84af47273cab2bef14de9ad9af980dd6246011c8e73d59965d57a42be9c04535f721b9758b0c9ce2983ce7181884ce712b6093c2c1ccf17647d22d7257e11e45747bfa3c55109cf23657ffeab66102990788a12da7527f0a750f478198866938d6649ecba5b6a5299e54e95ea94640f98411a2480c0d715272f67d5cc95584082153706e0fc20a172753c865c36f823c23010310dbcce8e2139806abc487b423020b15c4a4c36dd510c813fd30a7f5d7127fdbae6c7a1d328f675586e994ff9da36b8f9255cd4827db36c05c65686ebb34715077d3480a87d26583300c4d35ca8881f486f33d5e6446cac6714b7b3d60662e52c051faec6bd79d1a04383507bc922ac2df4d480c2b1ab68c244085fc8189d71169f7b09b850544f3506b94a9990db213f172d38a60d294278308f2145a698388ec358b8c69854f1f9dd8be0e2532d74a27eff0a9869682f8600403fbdc6901af8a0e149157859cd3d51f2953ec24abb7408ed124fed0b066fe15ca0b0ad8580802a84d04769acef54e0048832f6f7ab1c2f1c8ea36d4188ca25324df03aec303b6c8b3b3c0fcd0e3c7066c2031b04a10bab2da52669ac4ca0bce99e22b0026c9bbb30e894c2ff4e90a770e89c98f6b80f004c621f3f01415012f8fcff026030fca39c90830518a726b7c088a8ad256242d13f454fbb013678a8b1f9417f9f5fa6344e83e342a4a995834a5e05ab510a7c105883de2eca8894e9fdd1298fc22a1a95c4ec3c9a075b6308154097fed7c52515666190c5fe5b4bc6f97e89a2468034e9ab105816777ca21104f49b9acb49ee4512e40bb405b187d01d29e53a2600bd8cccccd6c25393fa5043e640c4487d41923c282940fcaae1d69c4de13df4194ed6b253d5adeb5677282e3de8889e87573705f38449f338db87727a790d761dcb94c2545ebf1e0db72e115429483f90062da3227015897d63aac0980338d0e8761363fe6e1bf869c49aff3329259d06dcbbcbd8ec0c06a58e046e1e23cabab1604d0028e26bb9a4d82e811b44c87b40924a6310d46a564e6c4d2ac68ea4814853268460ebc30cda2a79d4db981fa14a9aba6d2f607fb2ab189ae958261035b4508225a87a4a42c5944f930befce5b629a1ac4edc073a48a255a063495eee30bbc09b0dd34561c0a7c849a9d235c74f5b5b9d9a6ea0590865e7e5142e4e895dbbcdd5292105daad48ca1b68e002332fc4830108f15ea9fe8e67e7711aa4d7f41871f4e9fd15dadf3697f7f24085266c2ab925680d1250be452977103c2c56ee5f106ecfd4297c31b2939a4322e3efe42d3ed184a42251701fb4def7b478d841de16492a75277d5324a6f521bd63ef9c207c1446bad9b000e55ca3aa239b65d16e2829c40ae724494f496ed8d26873f58c60996c506a5cd8b624f474b5d8ac9c9559d47d633bd16258bd66c62c36480847f685f95c980846003b34a708e8b0554f127a508efe45f33e4fa9733efeaa32dbb33e32c173cf87f0c950d4840b246a841e7726f5f386a98697b6aeb7d5edc8763ec413bcf1f4b346059b8588af7cb98bcaa0e823c0f228e045028e72e28f6c9fa2c853f0124d65f7d48c8acd7eadb1c1f09e5b2092a1b79fdfffa5f11106b7d514d0a498da0981328fa2cdcbac89bca7eb05cb6ebc132afa3d3205fa7bde5100f1cec46a3a574c55058a83bc9bdcf7ddbc53e5b48f617b381b23a0d725bc6c07764dd0b2c3ed4ea6208eb9ba610a80c2d3637d8a24c4262e444b1340a8ddbcbc2c1b7b12de25debedbdb641b1b5d8efec16316f76557d864f94272392dad65b3e3b3215755df0f9f396f8441e6773a61dd730fdeade536295e61d1db16b7fed2d8c9425edac41c3ebd8ed1a16ff0bd97442356f636b36cb408fc356a28eed8134ce3cba1717e166e4c8b1f29e5bfef60c6c65999890179198049da8185a7548411779f646282ff07ae0fee46e974bd8ed7c57ad0cb6cc9b324a53cfd09097bfef6cbf5df7ffff4e9d32ff8679f7fffe3b7af7ffdfcf3a7cfdfbefeefb72f5f7fffcfafbffdf65f7ffbcb975f7ffbf5cb5afae55ffaf9fa77fffcc7af7ff9faf9e74fff77fdff9ffffe3f7ffcfef76ffff1ed8ffffefab77f7cfef9d373413e7ffbe3dbaf7ffdd39f7fc27bfdfba7ff070000ffff0300b0c002c507830000" + ], + "rawHeaders": { + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-Request-ID", + "alt-svc": "h3=\":443\"; ma=86400", + "cf-cache-status": "DYNAMIC", + "cf-ray": "913d132ccf812f43-LAX", + "connection": "close", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Tue, 18 Feb 2025 09:43:48 GMT", + "openai-model": "text-embedding-ada-002-v2", + "openai-organization": "anabasis-llc", + "openai-processing-ms": "108", + "openai-version": "2020-10-01", + "server": "cloudflare", + "set-cookie": "_cfuvid=O0XHiuX93wNyvY4BUcYzXcWSVDxe_cnX0SNjzuibOd8-1739871828641-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "transfer-encoding": "chunked", + "via": "envoy-router-dd569d47c-pmx5r", + "x-content-type-options": "nosniff", + "x-envoy-upstream-service-time": "52", + "x-ratelimit-limit-requests": "10000", + "x-ratelimit-limit-tokens": "5000000", + "x-ratelimit-remaining-requests": "9999", + "x-ratelimit-remaining-tokens": "4999997", + "x-ratelimit-reset-requests": "6ms", + "x-ratelimit-reset-tokens": "0s", + "x-request-id": "req_08930f92311198e1ba6770bf0e76dce0" + }, + "responseIsBinary": false + } +] diff --git a/src/services/code-indexer/__fixtures__/test.py b/src/services/code-indexer/__fixtures__/test.py new file mode 100644 index 00000000000..fc2953f272d --- /dev/null +++ b/src/services/code-indexer/__fixtures__/test.py @@ -0,0 +1,9 @@ +class TestClass: + def __init__(self): + pass + + def test_method(self): + return True + +def test_function(): + return 'test' diff --git a/src/services/code-indexer/__fixtures__/test.ts b/src/services/code-indexer/__fixtures__/test.ts new file mode 100644 index 00000000000..89f1ae5a831 --- /dev/null +++ b/src/services/code-indexer/__fixtures__/test.ts @@ -0,0 +1,15 @@ +class TestClass { + constructor() {} + + testMethod() { + return true + } +} + +function testFunction() { + return "test" +} + +const arrowFunc = () => { + console.log("arrow") +} diff --git a/src/services/code-indexer/__tests__/chunker.test.ts b/src/services/code-indexer/__tests__/chunker.test.ts new file mode 100644 index 00000000000..f3a7951fe35 --- /dev/null +++ b/src/services/code-indexer/__tests__/chunker.test.ts @@ -0,0 +1,43 @@ +// npx jest src/services/code-indexer/__tests__/chunker.test.ts + +import path from "path" + +import { getChunks } from "../chunker" + +describe("chunker", () => { + describe("getChunks", () => { + it("should chunk TypeScript code correctly", async () => { + const filepath = path.join(__dirname, "..", "__fixtures__", "test.ts") + const chunks = await getChunks(filepath) + + expect(chunks.length).toBeGreaterThan(0) + expect(chunks.some(({ type }) => type === "class_declaration")).toBe(true) + expect(chunks.some(({ type }) => type === "method_definition")).toBe(true) + expect(chunks.some(({ type }) => type === "function_declaration")).toBe(true) + expect(chunks.some(({ type }) => type === "arrow_function")).toBe(true) + + chunks.forEach((chunk) => { + expect(chunk.chunk).toBeTruthy() + expect(chunk.start).toBeDefined() + expect(chunk.end).toBeDefined() + expect(chunk.filepath).toBe(filepath) + }) + }) + + it("should chunk Python code correctly", async () => { + const filepath = path.join(__dirname, "..", "__fixtures__", "test.py") + const chunks = await getChunks(filepath) + + expect(chunks.length).toBeGreaterThan(0) + expect(chunks.some(({ type }) => type === "class_definition")).toBe(true) + expect(chunks.some(({ type }) => type === "function_definition")).toBe(true) + + chunks.forEach((chunk) => { + expect(chunk.chunk).toBeTruthy() + expect(chunk.start).toBeDefined() + expect(chunk.end).toBeDefined() + expect(chunk.filepath).toBe(filepath) + }) + }) + }) +}) diff --git a/src/services/code-indexer/__tests__/code-search.test.ts b/src/services/code-indexer/__tests__/code-search.test.ts new file mode 100644 index 00000000000..95c985d82a3 --- /dev/null +++ b/src/services/code-indexer/__tests__/code-search.test.ts @@ -0,0 +1,68 @@ +// npx jest src/services/code-indexer/__tests__/code-search.test.ts + +import path from "path" + +import nock from "nock" + +import { CodeSearch } from "../code-search" + +describe("CodeSearch", () => { + let savedKey: string | undefined + + beforeAll(() => { + savedKey = process.env.OPENAI_API_KEY + process.env.OPENAI_API_KEY = "fake" + + nock.back.fixtures = path.join(__dirname, "..", "__fixtures__") + // You can re-record the fixtures by setting the mode to "record" + // and running the tests with a real `OPENAI_API_KEY` in the environment. + nock.back.setMode("lockdown") + }) + + afterAll(() => { + process.env.OPENAI_API_KEY = savedKey + nock.back.setMode("wild") + }) + + describe("indexFile", () => { + it("should index a file", async () => { + const { nockDone } = await nock.back("indexFile.json") + + const filepath = path.join(__dirname, "..", "__fixtures__", "test.py") + const codeSearch = await CodeSearch.getInstance() + const chunks = await codeSearch.indexFile(filepath) + + expect(chunks.length).toBeGreaterThan(0) + const persistedChunk = await codeSearch.find(chunks[0].uuid) + expect(persistedChunk).toBeDefined() + expect(persistedChunk!.uuid).toBe(chunks[0].uuid) + + nockDone() + }) + }) + + describe("search", () => { + it("should find matches in indexed files", async () => { + const { nockDone } = await nock.back("search.json") + + const filepath = path.join(__dirname, "..", "__fixtures__", "test.ts") + const codeSearch = await CodeSearch.getInstance() + await codeSearch.indexFile(filepath) + + const results = await codeSearch.search({ + query: "testFunction", + distanceThreshold: 0.3, + }) + + expect(results[0]).toMatchObject({ + chunk: expect.stringContaining("testFunction()"), + start: 73, + end: 115, + type: "function_declaration", + filepath: expect.stringContaining("__fixtures__/test.ts"), + }) + + nockDone() + }) + }) +}) diff --git a/src/services/code-indexer/__tests__/uri.test.ts b/src/services/code-indexer/__tests__/uri.test.ts new file mode 100644 index 00000000000..be272d8d9fc --- /dev/null +++ b/src/services/code-indexer/__tests__/uri.test.ts @@ -0,0 +1,79 @@ +import { getCleanUriPath, getUriPathBasename, getFileExtensionFromBasename, getUriFileExtension } from "../uri" + +describe("getCleanUriPath", () => { + it("removes leading and trailing slashes", () => { + expect(getCleanUriPath("/path/to/file/")).toBe("path/to/file") + expect(getCleanUriPath("path/to/file/")).toBe("path/to/file") + expect(getCleanUriPath("/path/to/file")).toBe("path/to/file") + expect(getCleanUriPath("path/to/file")).toBe("path/to/file") + }) + + it("handles empty paths", () => { + expect(getCleanUriPath("")).toBe("") + expect(getCleanUriPath("/")).toBe("") + }) + + it("works with full URLs", () => { + expect(getCleanUriPath("https://example.com/path/to/file/")).toBe("path/to/file") + expect(getCleanUriPath("file:///path/to/file")).toBe("path/to/file") + }) +}) + +describe("getUriPathBasename", () => { + it("extracts the last path component", () => { + expect(getUriPathBasename("/path/to/file.txt")).toBe("file.txt") + expect(getUriPathBasename("path/to/file.txt")).toBe("file.txt") + expect(getUriPathBasename("file.txt")).toBe("file.txt") + }) + + it("handles encoded characters", () => { + expect(getUriPathBasename("/path/to/file%20with%20spaces.txt")).toBe("file with spaces.txt") + expect(getUriPathBasename("/path/to/file%2B%2B.cpp")).toBe("file++.cpp") + }) + + it("returns empty string for empty or root paths", () => { + expect(getUriPathBasename("")).toBe("") + expect(getUriPathBasename("/")).toBe("") + }) + + it("works with full URLs", () => { + expect(getUriPathBasename("https://example.com/path/file.txt")).toBe("file.txt") + expect(getUriPathBasename("file:///path/to/file.txt")).toBe("file.txt") + }) +}) + +describe("getFileExtensionFromBasename", () => { + it("extracts file extensions", () => { + expect(getFileExtensionFromBasename("file.txt")).toBe("txt") + expect(getFileExtensionFromBasename("file.TXT")).toBe("txt") + expect(getFileExtensionFromBasename("script.test.ts")).toBe("ts") + }) + + it("returns empty string for no extension", () => { + expect(getFileExtensionFromBasename("file")).toBe("") + expect(getFileExtensionFromBasename(".hidden")).toBe("hidden") + }) + + it("handles empty input", () => { + expect(getFileExtensionFromBasename("")).toBe("") + expect(getFileExtensionFromBasename(".")).toBe("") + }) +}) + +describe("getUriFileExtension", () => { + it("extracts extensions from URIs", () => { + expect(getUriFileExtension("https://example.com/path/file.txt")).toBe("txt") + expect(getUriFileExtension("file:///path/to/script.test.ts")).toBe("ts") + expect(getUriFileExtension("/path/to/file.TXT")).toBe("txt") + }) + + it("handles paths without extensions", () => { + expect(getUriFileExtension("https://example.com/path/file")).toBe("") + expect(getUriFileExtension("/path/to/file")).toBe("") + }) + + it("handles encoded characters", () => { + expect(getUriFileExtension("/path/to/file%2B%2B.cpp")).toBe("cpp") + expect(getUriFileExtension("/path/to/file%20with%20spaces.txt")).toBe("txt") + }) +}) diff --git a/src/services/code-indexer/chunker.ts b/src/services/code-indexer/chunker.ts new file mode 100644 index 00000000000..c7abfa87027 --- /dev/null +++ b/src/services/code-indexer/chunker.ts @@ -0,0 +1,201 @@ +import { readFile } from "fs/promises" +import path from "path" + +import { Parser, Language, Node } from "web-tree-sitter" + +import { getUriFileExtension } from "./uri" + +export type CodeChunk = { + chunk: string + start: number + end: number + type: string + filepath: string +} + +const supportedTypes = [ + "function_definition", + "class_definition", + "method_definition", + "function_declaration", + "class_declaration", + "method_declaration", + "arrow_function", + "export_statement", +] + +export async function getChunks(filepath: string): Promise { + const parser = await getParserForFile(filepath) + const sourceCode = await readFile(filepath, "utf-8") + const tree = parser.parse(sourceCode) + const chunks: CodeChunk[] = [] + + if (!tree) { + throw new Error(`Failed to parse file: ${filepath}`) + } + + const traverseNode = (node: Node) => { + const { type, startIndex, endIndex } = node + + if (supportedTypes.includes(node.type)) { + chunks.push({ + chunk: sourceCode.slice(startIndex, endIndex), + start: startIndex, + end: endIndex, + type, + filepath, + }) + } + + for (let child of node.children) { + if (child) { + traverseNode(child) + } + } + } + + traverseNode(tree.rootNode) + + return chunks +} + +export enum LanguageName { + CPP = "cpp", + C_SHARP = "c_sharp", + C = "c", + CSS = "css", + PHP = "php", + BASH = "bash", + JSON = "json", + TYPESCRIPT = "typescript", + TSX = "tsx", + ELM = "elm", + JAVASCRIPT = "javascript", + PYTHON = "python", + ELISP = "elisp", + ELIXIR = "elixir", + GO = "go", + EMBEDDED_TEMPLATE = "embedded_template", + HTML = "html", + JAVA = "java", + LUA = "lua", + OCAML = "ocaml", + QL = "ql", + RESCRIPT = "rescript", + RUBY = "ruby", + RUST = "rust", + SYSTEMRDL = "systemrdl", + TOML = "toml", + SOLIDITY = "solidity", +} + +export const supportedLanguages: { [key: string]: LanguageName } = { + cpp: LanguageName.CPP, + hpp: LanguageName.CPP, + cc: LanguageName.CPP, + cxx: LanguageName.CPP, + hxx: LanguageName.CPP, + cp: LanguageName.CPP, + hh: LanguageName.CPP, + inc: LanguageName.CPP, + cs: LanguageName.C_SHARP, + c: LanguageName.C, + h: LanguageName.C, + css: LanguageName.CSS, + php: LanguageName.PHP, + phtml: LanguageName.PHP, + php3: LanguageName.PHP, + php4: LanguageName.PHP, + php5: LanguageName.PHP, + php7: LanguageName.PHP, + phps: LanguageName.PHP, + "php-s": LanguageName.PHP, + bash: LanguageName.BASH, + sh: LanguageName.BASH, + json: LanguageName.JSON, + ts: LanguageName.TYPESCRIPT, + mts: LanguageName.TYPESCRIPT, + cts: LanguageName.TYPESCRIPT, + tsx: LanguageName.TSX, + elm: LanguageName.ELM, + js: LanguageName.JAVASCRIPT, + jsx: LanguageName.JAVASCRIPT, + mjs: LanguageName.JAVASCRIPT, + cjs: LanguageName.JAVASCRIPT, + py: LanguageName.PYTHON, + pyw: LanguageName.PYTHON, + pyi: LanguageName.PYTHON, + el: LanguageName.ELISP, + emacs: LanguageName.ELISP, + ex: LanguageName.ELIXIR, + exs: LanguageName.ELIXIR, + go: LanguageName.GO, + eex: LanguageName.EMBEDDED_TEMPLATE, + heex: LanguageName.EMBEDDED_TEMPLATE, + leex: LanguageName.EMBEDDED_TEMPLATE, + html: LanguageName.HTML, + htm: LanguageName.HTML, + java: LanguageName.JAVA, + lua: LanguageName.LUA, + ocaml: LanguageName.OCAML, + ml: LanguageName.OCAML, + mli: LanguageName.OCAML, + ql: LanguageName.QL, + res: LanguageName.RESCRIPT, + resi: LanguageName.RESCRIPT, + rb: LanguageName.RUBY, + erb: LanguageName.RUBY, + rs: LanguageName.RUST, + rdl: LanguageName.SYSTEMRDL, + toml: LanguageName.TOML, + sol: LanguageName.SOLIDITY, +} + +async function getParserForFile(filepath: string) { + await Parser.init() + const parser = new Parser() + const language = await getLanguageForFile(filepath) + + if (!language) { + throw new Error(`Unsupported language: ${filepath}`) + } + + parser.setLanguage(language) + return parser +} + +// Loading the wasm files to create a Language object is an expensive operation +// and with sufficient number of files can result in errors, instead keep a map +// of language name to Language object. +const languageMap = new Map() + +export async function getLanguageForFile(filepath: string) { + try { + await Parser.init() + const extension = getUriFileExtension(filepath) + const languageName = supportedLanguages[extension] + + if (!languageName) { + return undefined + } + + let language = languageMap.get(languageName) + + if (!language) { + const wasmPath = path.join( + process.env.NODE_ENV === "test" + ? path.join(process.cwd(), "node_modules", "tree-sitter-wasms", "out") + : path.join(__dirname, "tree-sitter-wasms"), + `tree-sitter-${supportedLanguages[extension]}.wasm`, + ) + + language = await Language.load(wasmPath) + languageMap.set(languageName, language) + } + + return language + } catch (e) { + console.debug("Unable to load language for file", filepath, e) + return undefined + } +} diff --git a/src/services/code-indexer/code-search.ts b/src/services/code-indexer/code-search.ts new file mode 100644 index 00000000000..cc1159073e1 --- /dev/null +++ b/src/services/code-indexer/code-search.ts @@ -0,0 +1,115 @@ +import { connect, Connection, Table } from "@lancedb/lancedb" +import "@lancedb/lancedb/embedding/openai" +import { LanceSchema, getRegistry } from "@lancedb/lancedb/embedding" +import { Utf8, Int32 } from "apache-arrow" +import { v4 as uuid } from "uuid" + +import { CodeChunk, getChunks } from "./chunker" + +export type IndexedCodeChunk = CodeChunk & { + uuid: string +} + +export type CodeSearchResult = IndexedCodeChunk & { + vector: Float32Array + _distance: number +} + +export class CodeSearch { + public readonly dbPath: string = "./lancedb" + public readonly tableName: string = "code_chunks" + + private connection?: Connection + + private _table?: Table + + public get table() { + if (!this._table) { + throw new Error("Table not initialized.") + } + + return this._table + } + + public async initialize() { + this.connection = await connect(this.dbPath) + + const fnCreator = getRegistry().get("openai") + + if (!fnCreator) { + throw new Error("OpenAI embedding function not found.") + } + + const embeddingFn = fnCreator.create({ + model: "text-embedding-ada-002", + }) + + try { + this._table = await this.connection.openTable(this.tableName) + } catch { + const schema = LanceSchema({ + uuid: new Utf8(), + chunk: embeddingFn.sourceField(new Utf8()), + start: new Int32(), + end: new Int32(), + type: new Utf8(), + filepath: new Utf8(), + vector: embeddingFn.vectorField(), + }) + + this._table = await this.connection.createEmptyTable(this.tableName, schema, { mode: "overwrite" }) + + this.table.createIndex("uuid") + } + } + + public async indexFile(filepath: string) { + const chunks = await getChunks(filepath) + + const records = chunks.map((chunk) => ({ + uuid: uuid(), + ...chunk, + })) + + await this.table.add(records) + return records + } + + public async find(uuid: string): Promise { + const result = await this.table.query().where(`uuid == '${uuid}'`).limit(1).toArray() + + return result[0] + } + + public async search({ + query, + limit = 5, + distanceThreshold, + }: { + query: string + limit?: number + distanceThreshold?: number + }): Promise { + const results = await this.table.search(query).limit(limit).toArray() + + return distanceThreshold ? results.filter(({ _distance }) => _distance <= distanceThreshold) : results + } + + public async clear() { + if (this.connection) { + await this.connection.dropTable(this.tableName) + await this.initialize() + } + } + + private static instance: CodeSearch + + public static async getInstance() { + if (!this.instance) { + this.instance = new CodeSearch() + await this.instance.initialize() + } + + return this.instance + } +} diff --git a/src/services/code-indexer/uri.ts b/src/services/code-indexer/uri.ts new file mode 100644 index 00000000000..351ce42a75e --- /dev/null +++ b/src/services/code-indexer/uri.ts @@ -0,0 +1,22 @@ +import * as URI from "uri-js" + +export function getCleanUriPath(uri: string) { + const path = URI.parse(uri).path ?? "" + const clean = path.replace(/^\//, "") // Remove start slash. + return clean.replace(/\/$/, "") // Remove end slash. +} + +export function getUriPathBasename(uri: string): string { + const path = getCleanUriPath(uri) + const basename = path.split("/").pop() || "" + return decodeURIComponent(basename) +} + +export function getFileExtensionFromBasename(basename: string) { + const parts = basename.split(".") + return parts.length < 2 ? "" : (parts.slice(-1)[0] ?? "").toLowerCase() +} + +export function getUriFileExtension(uri: string) { + return getFileExtensionFromBasename(getUriPathBasename(uri)) +} diff --git a/src/services/tree-sitter/__tests__/index.test.ts b/src/services/tree-sitter/__tests__/index.test.ts index 4a5782dcb1e..36c7c174736 100644 --- a/src/services/tree-sitter/__tests__/index.test.ts +++ b/src/services/tree-sitter/__tests__/index.test.ts @@ -1,15 +1,11 @@ +// npx jest src/services/tree-sitter/__tests__/index.test.ts + import { parseSourceCodeForDefinitionsTopLevel } from "../index" import { listFiles } from "../../glob/list-files" -import { loadRequiredLanguageParsers } from "../languageParser" -import { fileExistsAtPath } from "../../../utils/fs" -import * as fs from "fs/promises" -import * as path from "path" +import { fileExistsAtPath, readFile } from "../../../utils/fs" -// Mock dependencies jest.mock("../../glob/list-files") -jest.mock("../languageParser") jest.mock("../../../utils/fs") -jest.mock("fs/promises") describe("Tree-sitter Service", () => { beforeEach(() => { @@ -34,32 +30,8 @@ describe("Tree-sitter Service", () => { it("should parse TypeScript files correctly", async () => { const mockFiles = ["/test/path/file1.ts", "/test/path/file2.tsx", "/test/path/readme.md"] - ;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]) - - const mockParser = { - parse: jest.fn().mockReturnValue({ - rootNode: "mockNode", - }), - } - - const mockQuery = { - captures: jest.fn().mockReturnValue([ - { - node: { - startPosition: { row: 0 }, - endPosition: { row: 0 }, - }, - name: "name.definition", - }, - ]), - } - - ;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({ - ts: { parser: mockParser, query: mockQuery }, - tsx: { parser: mockParser, query: mockQuery }, - }) - ;(fs.readFile as jest.Mock).mockResolvedValue("export class TestClass {\n constructor() {}\n}") + ;(readFile as jest.Mock).mockResolvedValue("export class TestClass {\n constructor() {}\n}") const result = await parseSourceCodeForDefinitionsTopLevel("/test/path") @@ -72,39 +44,8 @@ describe("Tree-sitter Service", () => { it("should handle multiple definition types", async () => { const mockFiles = ["/test/path/file.ts"] ;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]) - - const mockParser = { - parse: jest.fn().mockReturnValue({ - rootNode: "mockNode", - }), - } - - const mockQuery = { - captures: jest.fn().mockReturnValue([ - { - node: { - startPosition: { row: 0 }, - endPosition: { row: 0 }, - }, - name: "name.definition.class", - }, - { - node: { - startPosition: { row: 2 }, - endPosition: { row: 2 }, - }, - name: "name.definition.function", - }, - ]), - } - - ;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({ - ts: { parser: mockParser, query: mockQuery }, - }) - const fileContent = "class TestClass {\n" + " constructor() {}\n" + " testMethod() {}\n" + "}" - - ;(fs.readFile as jest.Mock).mockResolvedValue(fileContent) + ;(readFile as jest.Mock).mockResolvedValue(fileContent) const result = await parseSourceCodeForDefinitionsTopLevel("/test/path") @@ -116,21 +57,7 @@ describe("Tree-sitter Service", () => { it("should handle parsing errors gracefully", async () => { const mockFiles = ["/test/path/file.ts"] ;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]) - - const mockParser = { - parse: jest.fn().mockImplementation(() => { - throw new Error("Parsing error") - }), - } - - const mockQuery = { - captures: jest.fn(), - } - - ;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({ - ts: { parser: mockParser, query: mockQuery }, - }) - ;(fs.readFile as jest.Mock).mockResolvedValue("invalid code") + ;(readFile as jest.Mock).mockResolvedValue("invalid code") const result = await parseSourceCodeForDefinitionsTopLevel("/test/path") expect(result).toBe("No source code definitions found.") @@ -141,25 +68,12 @@ describe("Tree-sitter Service", () => { .fill(0) .map((_, i) => `/test/path/file${i}.ts`) ;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]) - - const mockParser = { - parse: jest.fn().mockReturnValue({ - rootNode: "mockNode", - }), - } - - const mockQuery = { - captures: jest.fn().mockReturnValue([]), - } - - ;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({ - ts: { parser: mockParser, query: mockQuery }, - }) + ;(readFile as jest.Mock).mockResolvedValue("") await parseSourceCodeForDefinitionsTopLevel("/test/path") - // Should only process first 50 files - expect(mockParser.parse).toHaveBeenCalledTimes(50) + // Should only process first 50 files. + expect(readFile).toHaveBeenCalledTimes(50) }) it("should handle various supported file extensions", async () => { @@ -172,73 +86,42 @@ describe("Tree-sitter Service", () => { ] ;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]) - - const mockParser = { - parse: jest.fn().mockReturnValue({ - rootNode: "mockNode", - }), - } - - const mockQuery = { - captures: jest.fn().mockReturnValue([ - { - node: { - startPosition: { row: 0 }, - endPosition: { row: 0 }, - }, - name: "name", - }, - ]), - } - - ;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({ - js: { parser: mockParser, query: mockQuery }, - py: { parser: mockParser, query: mockQuery }, - rs: { parser: mockParser, query: mockQuery }, - cpp: { parser: mockParser, query: mockQuery }, - go: { parser: mockParser, query: mockQuery }, + ;(readFile as jest.Mock).mockImplementation((path: string) => { + if (path.endsWith(".js")) return Promise.resolve("function jsTest() { return true; }") + if (path.endsWith(".py")) return Promise.resolve("def py_test():\n return True") + if (path.endsWith(".rs")) return Promise.resolve("fn rust_test() -> bool {\n true\n}") + if (path.endsWith(".cpp")) return Promise.resolve("bool cppTest() {\n return true;\n}") + if (path.endsWith(".go")) return Promise.resolve("func goTest() bool {\n return true\n}") + return Promise.resolve("") }) - ;(fs.readFile as jest.Mock).mockResolvedValue("function test() {}") const result = await parseSourceCodeForDefinitionsTopLevel("/test/path") + console.log("result", result) expect(result).toContain("script.js") + expect(result).toContain("jsTest()") + expect(result).toContain("app.py") + expect(result).toContain("py_test()") + expect(result).toContain("main.rs") + expect(result).toContain("rust_test()") + expect(result).toContain("program.cpp") + expect(result).toContain("cppTest()") + expect(result).toContain("code.go") + expect(result).toContain("goTest()") }) it("should normalize paths in output", async () => { const mockFiles = ["/test/path/dir\\file.ts"] ;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]) - - const mockParser = { - parse: jest.fn().mockReturnValue({ - rootNode: "mockNode", - }), - } - - const mockQuery = { - captures: jest.fn().mockReturnValue([ - { - node: { - startPosition: { row: 0 }, - endPosition: { row: 0 }, - }, - name: "name", - }, - ]), - } - - ;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({ - ts: { parser: mockParser, query: mockQuery }, - }) - ;(fs.readFile as jest.Mock).mockResolvedValue("class Test {}") + ;(readFile as jest.Mock).mockResolvedValue("class Test {}") const result = await parseSourceCodeForDefinitionsTopLevel("/test/path") - // Should use forward slashes regardless of platform + // Should use forward slashes regardless of platform. expect(result).toContain("dir/file.ts") expect(result).not.toContain("dir\\file.ts") }) diff --git a/src/services/tree-sitter/__tests__/languageParser.test.ts b/src/services/tree-sitter/__tests__/languageParser.test.ts index 1b92d81b6be..858c9d7f5a1 100644 --- a/src/services/tree-sitter/__tests__/languageParser.test.ts +++ b/src/services/tree-sitter/__tests__/languageParser.test.ts @@ -1,118 +1,106 @@ +// npx jest src/services/tree-sitter/__tests__/languageParser.test.ts + +import { Parser, Language } from "web-tree-sitter" + import { loadRequiredLanguageParsers } from "../languageParser" -import Parser from "web-tree-sitter" - -// Mock web-tree-sitter -const mockSetLanguage = jest.fn() -jest.mock("web-tree-sitter", () => { - return { - __esModule: true, - default: jest.fn().mockImplementation(() => ({ - setLanguage: mockSetLanguage, - })), - } -}) -// Add static methods to Parser mock -const ParserMock = Parser as jest.MockedClass -ParserMock.init = jest.fn().mockResolvedValue(undefined) -ParserMock.Language = { - load: jest.fn().mockResolvedValue({ - query: jest.fn().mockReturnValue("mockQuery"), - }), - prototype: {}, // Add required prototype property -} as unknown as typeof Parser.Language - -describe("Language Parser", () => { +describe("loadRequiredLanguageParsers", () => { beforeEach(() => { jest.clearAllMocks() }) - describe("loadRequiredLanguageParsers", () => { - it("should initialize parser only once", async () => { - const files = ["test.js", "test2.js"] - await loadRequiredLanguageParsers(files) - await loadRequiredLanguageParsers(files) - - expect(ParserMock.init).toHaveBeenCalledTimes(1) - }) - - it("should load JavaScript parser for .js and .jsx files", async () => { - const files = ["test.js", "test.jsx"] - const parsers = await loadRequiredLanguageParsers(files) - - expect(ParserMock.Language.load).toHaveBeenCalledWith( - expect.stringContaining("tree-sitter-javascript.wasm"), - ) - expect(parsers.js).toBeDefined() - expect(parsers.jsx).toBeDefined() - expect(parsers.js.query).toBeDefined() - expect(parsers.jsx.query).toBeDefined() - }) - - it("should load TypeScript parser for .ts and .tsx files", async () => { - const files = ["test.ts", "test.tsx"] - const parsers = await loadRequiredLanguageParsers(files) - - expect(ParserMock.Language.load).toHaveBeenCalledWith( - expect.stringContaining("tree-sitter-typescript.wasm"), - ) - expect(ParserMock.Language.load).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-tsx.wasm")) - expect(parsers.ts).toBeDefined() - expect(parsers.tsx).toBeDefined() - }) - - it("should load Python parser for .py files", async () => { - const files = ["test.py"] - const parsers = await loadRequiredLanguageParsers(files) - - expect(ParserMock.Language.load).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-python.wasm")) - expect(parsers.py).toBeDefined() - }) - - it("should load multiple language parsers as needed", async () => { - const files = ["test.js", "test.py", "test.rs", "test.go"] - const parsers = await loadRequiredLanguageParsers(files) - - expect(ParserMock.Language.load).toHaveBeenCalledTimes(4) - expect(parsers.js).toBeDefined() - expect(parsers.py).toBeDefined() - expect(parsers.rs).toBeDefined() - expect(parsers.go).toBeDefined() - }) - - it("should handle C/C++ files correctly", async () => { - const files = ["test.c", "test.h", "test.cpp", "test.hpp"] - const parsers = await loadRequiredLanguageParsers(files) - - expect(ParserMock.Language.load).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-c.wasm")) - expect(ParserMock.Language.load).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-cpp.wasm")) - expect(parsers.c).toBeDefined() - expect(parsers.h).toBeDefined() - expect(parsers.cpp).toBeDefined() - expect(parsers.hpp).toBeDefined() - }) - - it("should throw error for unsupported file extensions", async () => { - const files = ["test.unsupported"] - - await expect(loadRequiredLanguageParsers(files)).rejects.toThrow("Unsupported language: unsupported") - }) - - it("should load each language only once for multiple files", async () => { - const files = ["test1.js", "test2.js", "test3.js"] - await loadRequiredLanguageParsers(files) - - expect(ParserMock.Language.load).toHaveBeenCalledTimes(1) - expect(ParserMock.Language.load).toHaveBeenCalledWith( - expect.stringContaining("tree-sitter-javascript.wasm"), - ) - }) - - it("should set language for each parser instance", async () => { - const files = ["test.js", "test.py"] - await loadRequiredLanguageParsers(files) - - expect(mockSetLanguage).toHaveBeenCalledTimes(2) - }) + it("should initialize parser only once", async () => { + const parserSpy = jest.spyOn(Parser, "init") + + const files = ["test.js", "test2.js"] + await loadRequiredLanguageParsers(files) + await loadRequiredLanguageParsers(files) + + expect(parserSpy).toHaveBeenCalledTimes(1) + }) + + it("should load JavaScript parser for .js and .jsx files", async () => { + const languageSpy = jest.spyOn(Language, "load") + + const files = ["test.js", "test.jsx"] + const parsers = await loadRequiredLanguageParsers(files) + + expect(languageSpy).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-javascript.wasm")) + expect(parsers.js).toBeDefined() + expect(parsers.jsx).toBeDefined() + expect(parsers.js.query).toBeDefined() + expect(parsers.jsx.query).toBeDefined() + }) + + it("should load TypeScript parser for .ts and .tsx files", async () => { + const languageSpy = jest.spyOn(Language, "load") + + const files = ["test.ts", "test.tsx"] + const parsers = await loadRequiredLanguageParsers(files) + + expect(languageSpy).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-typescript.wasm")) + expect(languageSpy).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-tsx.wasm")) + expect(parsers.ts).toBeDefined() + expect(parsers.tsx).toBeDefined() + }) + + it("should load Python parser for .py files", async () => { + const languageSpy = jest.spyOn(Language, "load") + + const files = ["test.py"] + const parsers = await loadRequiredLanguageParsers(files) + + expect(languageSpy).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-python.wasm")) + expect(parsers.py).toBeDefined() + }) + + it("should load multiple language parsers as needed", async () => { + const languageSpy = jest.spyOn(Language, "load") + + const files = ["test.js", "test.py", "test.rs", "test.go"] + const parsers = await loadRequiredLanguageParsers(files) + + expect(languageSpy).toHaveBeenCalledTimes(4) + expect(parsers.js).toBeDefined() + expect(parsers.py).toBeDefined() + expect(parsers.rs).toBeDefined() + expect(parsers.go).toBeDefined() + }) + + it("should handle C/C++ files correctly", async () => { + const languageSpy = jest.spyOn(Language, "load") + + const files = ["test.c", "test.h", "test.cpp", "test.hpp"] + const parsers = await loadRequiredLanguageParsers(files) + + expect(languageSpy).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-c.wasm")) + expect(languageSpy).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-cpp.wasm")) + expect(parsers.c).toBeDefined() + expect(parsers.h).toBeDefined() + expect(parsers.cpp).toBeDefined() + expect(parsers.hpp).toBeDefined() + }) + + it("should throw error for unsupported file extensions", async () => { + const files = ["test.unsupported"] + + await expect(loadRequiredLanguageParsers(files)).rejects.toThrow("Unsupported language: unsupported") + }) + + it("should load each language only once for multiple files", async () => { + const languageSpy = jest.spyOn(Language, "load") + + const files = ["test1.js", "test2.js", "test3.js"] + await loadRequiredLanguageParsers(files) + + expect(languageSpy).toHaveBeenCalledTimes(1) + expect(languageSpy).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-javascript.wasm")) + }) + + it("should set language for each parser instance", async () => { + const files = ["test.js", "test.py"] + const parsers = await loadRequiredLanguageParsers(files) + + expect(Object.keys(parsers)).toEqual(["js", "py"]) }) }) diff --git a/src/services/tree-sitter/index.ts b/src/services/tree-sitter/index.ts index 83e02ac6158..8583dd3eb72 100644 --- a/src/services/tree-sitter/index.ts +++ b/src/services/tree-sitter/index.ts @@ -1,8 +1,8 @@ -import * as fs from "fs/promises" import * as path from "path" + import { listFiles } from "../glob/list-files" import { LanguageParser, loadRequiredLanguageParsers } from "./languageParser" -import { fileExistsAtPath } from "../../utils/fs" +import { fileExistsAtPath, readFile } from "../../utils/fs" // TODO: implement caching behavior to avoid having to keep analyzing project for new tasks. export async function parseSourceCodeForDefinitionsTopLevel(dirPath: string): Promise { @@ -26,6 +26,7 @@ export async function parseSourceCodeForDefinitionsTopLevel(dirPath: string): Pr // const filesWithoutDefinitions: string[] = [] for (const file of filesToParse) { const definitions = await parseFile(file, languageParsers) + console.log(`file: ${file}, definitions =`, definitions) if (definitions) { result += `${path.relative(dirPath, file).toPosix()}\n${definitions}\n` } @@ -96,10 +97,11 @@ This approach allows us to focus on the most relevant parts of the code (defined - https://tree-sitter.github.io/tree-sitter/code-navigation-systems */ async function parseFile(filePath: string, languageParsers: LanguageParser): Promise { - const fileContent = await fs.readFile(filePath, "utf8") + const fileContent = await readFile(filePath) const ext = path.extname(filePath).toLowerCase().slice(1) const { parser, query } = languageParsers[ext] || {} + if (!parser || !query) { return `Unsupported file type: ${filePath}` } @@ -110,6 +112,10 @@ async function parseFile(filePath: string, languageParsers: LanguageParser): Pro // Parse the file content into an Abstract Syntax Tree (AST), a tree-like representation of the code const tree = parser.parse(fileContent) + if (!tree) { + throw new Error(`Failed to parse file: ${filePath}`) + } + // Apply the query to the AST and get the captures // Captures are specific parts of the AST that match our query patterns, each capture represents a node in the AST that we're interested in. const captures = query.captures(tree.rootNode) @@ -156,5 +162,6 @@ async function parseFile(filePath: string, languageParsers: LanguageParser): Pro if (formattedOutput.length > 0) { return `|----\n${formattedOutput}|----\n` } + return undefined } diff --git a/src/services/tree-sitter/languageParser.ts b/src/services/tree-sitter/languageParser.ts index 2d791b39a8d..0fbbc41997f 100644 --- a/src/services/tree-sitter/languageParser.ts +++ b/src/services/tree-sitter/languageParser.ts @@ -1,5 +1,7 @@ import * as path from "path" -import Parser from "web-tree-sitter" + +import { Parser, Language, Query } from "web-tree-sitter" + import { javascriptQuery, typescriptQuery, @@ -18,12 +20,17 @@ import { export interface LanguageParser { [key: string]: { parser: Parser - query: Parser.Query + query: Query } } async function loadLanguage(langName: string) { - return await Parser.Language.load(path.join(__dirname, `tree-sitter-${langName}.wasm`)) + if (process.env.NODE_ENV === "test") { + const wasmPath = path.join(process.cwd(), "node_modules", "tree-sitter-wasms", "out") + return await Language.load(path.join(wasmPath, `tree-sitter-${langName}.wasm`)) + } + + return await Language.load(path.join(__dirname, `tree-sitter-${langName}.wasm`)) } let isParserInitialized = false @@ -61,71 +68,75 @@ export async function loadRequiredLanguageParsers(filesToParse: string[]): Promi await initializeParser() const extensionsToLoad = new Set(filesToParse.map((file) => path.extname(file).toLowerCase().slice(1))) const parsers: LanguageParser = {} + for (const ext of extensionsToLoad) { - let language: Parser.Language - let query: Parser.Query + let language: Language + let query: Query + switch (ext) { case "js": case "jsx": language = await loadLanguage("javascript") - query = language.query(javascriptQuery) + query = new Query(language, javascriptQuery) break case "ts": language = await loadLanguage("typescript") - query = language.query(typescriptQuery) + query = new Query(language, typescriptQuery) break case "tsx": language = await loadLanguage("tsx") - query = language.query(typescriptQuery) + query = new Query(language, typescriptQuery) break case "py": language = await loadLanguage("python") - query = language.query(pythonQuery) + query = new Query(language, pythonQuery) break case "rs": language = await loadLanguage("rust") - query = language.query(rustQuery) + query = new Query(language, rustQuery) break case "go": language = await loadLanguage("go") - query = language.query(goQuery) + query = new Query(language, goQuery) break case "cpp": case "hpp": language = await loadLanguage("cpp") - query = language.query(cppQuery) + query = new Query(language, cppQuery) break case "c": case "h": language = await loadLanguage("c") - query = language.query(cQuery) + query = new Query(language, cQuery) break case "cs": language = await loadLanguage("c_sharp") - query = language.query(csharpQuery) + query = new Query(language, csharpQuery) break case "rb": language = await loadLanguage("ruby") - query = language.query(rubyQuery) + query = new Query(language, rubyQuery) break case "java": language = await loadLanguage("java") - query = language.query(javaQuery) + query = new Query(language, javaQuery) break case "php": language = await loadLanguage("php") - query = language.query(phpQuery) + query = new Query(language, phpQuery) break case "swift": language = await loadLanguage("swift") - query = language.query(swiftQuery) + query = new Query(language, swiftQuery) break default: throw new Error(`Unsupported language: ${ext}`) } + const parser = new Parser() parser.setLanguage(language) parsers[ext] = { parser, query } } + return parsers } diff --git a/src/utils/fs.ts b/src/utils/fs.ts index 9f7af84e4af..334ef02bb8e 100644 --- a/src/utils/fs.ts +++ b/src/utils/fs.ts @@ -45,3 +45,15 @@ export async function fileExistsAtPath(filePath: string): Promise { return false } } + +/** + * Reads the contents of a file. Carved out specifically for testing purposes; + * we can easily mock fs.readFile without mocking everything in the fs module. + * + * @param filePath - The path to the file to read. + * @returns A promise that resolves to the file's contents. + */ +export async function readFile(filePath: string): Promise { + const content = await fs.readFile(filePath, "utf-8") + return content +} diff --git a/tsconfig.integration.json b/tsconfig.integration.json index 0de0ea736a9..c9065fc8f46 100644 --- a/tsconfig.integration.json +++ b/tsconfig.integration.json @@ -9,9 +9,10 @@ "strict": true, "skipLibCheck": true, "useUnknownInCatchVariables": false, - "rootDir": "src", + "noImplicitAny": false, + "rootDir": "integration-tests", "outDir": "out-integration" }, - "include": ["**/*.ts"], + "include": ["integration-tests/**/*.ts"], "exclude": [".vscode-test", "benchmark", "dist", "**/node_modules/**", "out", "out-integration", "webview-ui"] } diff --git a/tsconfig.json b/tsconfig.json index 1d70336fc17..492adb3440e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,10 @@ "strict": true, "target": "es2022", "useDefineForClassFields": true, - "useUnknownInCatchVariables": false + "useUnknownInCatchVariables": false, + "paths": { + "web-tree-sitter": ["./node_modules/web-tree-sitter/web-tree-sitter.d.ts"] + } }, "include": ["src/**/*", "scripts/**/*", ".changeset/**/*"], "exclude": ["node_modules", ".vscode-test", "webview-ui"]