diff --git a/.gitignore b/.gitignore index 422ec15ad..f335b9ecf 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ cache artifact _deno.lock -!/jumble/integration/cache/llm-api-cache +!/jumble/integration/cache .claude-prompt.tmp .pr-desc.tmp dist/ \ No newline at end of file diff --git a/charm/src/iframe/static.ts b/charm/src/iframe/static.ts index 95f3ee45b..76b598996 100644 --- a/charm/src/iframe/static.ts +++ b/charm/src/iframe/static.ts @@ -165,32 +165,6 @@ ${JSON.stringify(libraries)} }); })(); - window.grabJson = (gstr) => { - // Function to extract and parse JSON from a string - // This handles both raw JSON strings and code blocks with JSON - const jsonRegex = /\`\`\`(?:json)?\s*([\s\S]*?)\s*\`\`\`|({[\s\S]*})/; - const match = gstr.match(jsonRegex); - - if (match) { - // Use the first matching group that contains content - const jsonStr = match[1] || match[2]; - try { - return JSON.parse(jsonStr); - } catch (e) { - console.error("Failed to parse JSON:", e); - return null; - } - } else { - // If no JSON block found, attempt to parse the entire string - try { - return JSON.parse(gstr); - } catch (e) { - console.error("No valid JSON found in string"); - return null; - } - } - } - // Define readWebpage utility with React available window.readWebpage = (function() { const inflight = []; @@ -673,12 +647,23 @@ async function fetchLLMResponse() { system: 'Translate all the messages to emojis, reply in JSON.', messages: ['Hi', 'How can I help you today?', 'tell me a joke'] }; - // grabJson is available on the window, string -> JSON - const result = grabJson(await llm(promptPayload)); + const result = await llm(promptPayload) console.log('LLM responded:', result); } \`\`\` +If you need JSON to be returned from the LLM, you can enable the \`mode: 'json'\` in the \`promptPayload\`. + +\`\`\`jsx +const promptPayload = { + system: 'Translate all the messages to emojis, reply in JSON.', + messages: ['Hi', 'How can I help you today?', 'tell me a joke'], + mode: 'json' +}; +const result = await llm(promptPayload); +console.log('JSON response from llm:', result); +\`\`\` + ## 3. readWebpage Function \`\`\`jsx diff --git a/deno.lock b/deno.lock index db4bb3c92..ec81b1290 100644 --- a/deno.lock +++ b/deno.lock @@ -45,9 +45,7 @@ "jsr:@std/streams@^1.0.9": "1.0.9", "jsr:@std/testing@1": "1.0.9", "jsr:@zip-js/zip-js@^2.7.52": "2.7.57", - "npm:@ai-sdk/amazon-bedrock@^1.1.6": "1.1.6_zod@3.24.2", "npm:@ai-sdk/anthropic@^1.1.6": "1.1.15_zod@3.24.2", - "npm:@ai-sdk/cerebras@~0.1.8": "0.1.13_zod@3.24.2", "npm:@ai-sdk/google-vertex@^2.1.12": "2.1.24_zod@3.24.2", "npm:@ai-sdk/groq@^1.1.7": "1.1.12_zod@3.24.2", "npm:@ai-sdk/openai@^1.1.9": "1.2.2_zod@3.24.2", @@ -316,29 +314,11 @@ } }, "npm": { - "@ai-sdk/amazon-bedrock@1.1.6_zod@3.24.2": { - "integrity": "sha512-h6SJWpku+i8OsSz0A4RT2g2uD+3E0SUgWHsWRIpxmPNgM1DnH6lgSby5sxqAZDY5xJyJtRFW5vB9G3GEBjHy/g==", - "dependencies": [ - "@ai-sdk/provider@1.0.7", - "@ai-sdk/provider-utils@2.1.6_zod@3.24.2", - "@aws-sdk/client-bedrock-runtime", - "zod" - ] - }, "@ai-sdk/anthropic@1.1.15_zod@3.24.2": { "integrity": "sha512-KqI2vjEPLieBmZh+QIB0055JGUh9F7QcMdqj+dOGrtBawd0zjhZ2uBxP8Ghvl4WhbuTEOo54mlAg7RZO0eP2Tg==", "dependencies": [ - "@ai-sdk/provider@1.0.10", - "@ai-sdk/provider-utils@2.1.11_zod@3.24.2", - "zod" - ] - }, - "@ai-sdk/cerebras@0.1.13_zod@3.24.2": { - "integrity": "sha512-jrNKpZx1LzG7+t5ARHR62PhUJ1pB6HDj7E94eTRaVZjZ6+1YkATTz5MplNsv3Eyy91JCr3GQZEyPf0dDiwKwbA==", - "dependencies": [ - "@ai-sdk/openai-compatible", - "@ai-sdk/provider@1.0.10", - "@ai-sdk/provider-utils@2.1.11_zod@3.24.2", + "@ai-sdk/provider", + "@ai-sdk/provider-utils", "zod" ] }, @@ -347,8 +327,8 @@ "dependencies": [ "@ai-sdk/anthropic", "@ai-sdk/google", - "@ai-sdk/provider@1.0.10", - "@ai-sdk/provider-utils@2.1.11_zod@3.24.2", + "@ai-sdk/provider", + "@ai-sdk/provider-utils", "google-auth-library", "zod" ] @@ -356,49 +336,31 @@ "@ai-sdk/google@1.1.20_zod@3.24.2": { "integrity": "sha512-vsYtmFYy5vQAovWWAqb9XoLZ01lmcfpfxviY8lMV92YwwbLLhYcaPQ4dKaaKo0tSksCWsO2Qehw8bN3eBxmZdA==", "dependencies": [ - "@ai-sdk/provider@1.0.10", - "@ai-sdk/provider-utils@2.1.11_zod@3.24.2", + "@ai-sdk/provider", + "@ai-sdk/provider-utils", "zod" ] }, "@ai-sdk/groq@1.1.12_zod@3.24.2": { "integrity": "sha512-Cjvqrd1RLpdo6k7KQCENLiEDIYjXQdkfWcsBWn9WiGkiXgLyOwBZhxV0HK1jJKl17zO2AU4+QJODaqR9rQkLRA==", "dependencies": [ - "@ai-sdk/provider@1.0.10", - "@ai-sdk/provider-utils@2.1.11_zod@3.24.2", - "zod" - ] - }, - "@ai-sdk/openai-compatible@0.1.13_zod@3.24.2": { - "integrity": "sha512-hgj6BdvasVXCTmJwbsiWo+e626GkmEBJKG8PYwpVq7moLWj93wJnfBNlDjxVjhZ32d5KGT32RIMZjqaX8QkClg==", - "dependencies": [ - "@ai-sdk/provider@1.0.10", - "@ai-sdk/provider-utils@2.1.11_zod@3.24.2", + "@ai-sdk/provider", + "@ai-sdk/provider-utils", "zod" ] }, "@ai-sdk/openai@1.2.2_zod@3.24.2": { "integrity": "sha512-5355FLtSOH8sz9N9fsSwWpTaEgfqKOPMMHgSs1j4Aih5kQc9PhJ/oAPZuH308c/ktrbx6GcCW/hVrITimYsQhQ==", "dependencies": [ - "@ai-sdk/provider@1.0.10", - "@ai-sdk/provider-utils@2.1.11_zod@3.24.2", + "@ai-sdk/provider", + "@ai-sdk/provider-utils", "zod" ] }, "@ai-sdk/provider-utils@2.1.11_zod@3.24.2": { "integrity": "sha512-lMnXA5KaRJidzW7gQmlo/SnX6D+AKk5GxHFcQtOaGOSJNmu/qcNZc1rGaO7K5qW52OvCLXtnWudR4cc/FvMpVQ==", "dependencies": [ - "@ai-sdk/provider@1.0.10", - "eventsource-parser@3.0.0", - "nanoid", - "secure-json-parse", - "zod" - ] - }, - "@ai-sdk/provider-utils@2.1.6_zod@3.24.2": { - "integrity": "sha512-Pfyaj0QZS22qyVn5Iz7IXcJ8nKIKlu2MeSAdKJzTwkAks7zdLaKVB+396Rqcp1bfQnxl7vaduQVMQiXUrgK8Gw==", - "dependencies": [ - "@ai-sdk/provider@1.0.7", + "@ai-sdk/provider", "eventsource-parser@3.0.0", "nanoid", "secure-json-parse", @@ -411,16 +373,10 @@ "json-schema" ] }, - "@ai-sdk/provider@1.0.7": { - "integrity": "sha512-q1PJEZ0qD9rVR+8JFEd01/QM++csMT5UVwYXSN2u54BrVw/D8TZLTeg2FEfKK00DgAx0UtWd8XOhhwITP9BT5g==", - "dependencies": [ - "json-schema" - ] - }, "@ai-sdk/react@1.1.21_react@18.3.1_zod@3.24.2": { "integrity": "sha512-VKgqzG5wKjyLhROiFhRdyMuDcGu5QPfdLU5J7ovqR1HecknxymL3nCXsxWbAaiZ0khm2EsST6L6zwUbriZrKgg==", "dependencies": [ - "@ai-sdk/provider-utils@2.1.11_zod@3.24.2", + "@ai-sdk/provider-utils", "@ai-sdk/ui-utils", "react", "swr", @@ -431,8 +387,8 @@ "@ai-sdk/ui-utils@1.1.17_zod@3.24.2": { "integrity": "sha512-fCnp/wntZGqPf6tiCmhuQoSDLSBhXoI5DU2JX4As96EO870+jliE6ozvYUwYOZC6Ta2OKAjjWPcSP7HeHX0b+g==", "dependencies": [ - "@ai-sdk/provider@1.0.10", - "@ai-sdk/provider-utils@2.1.11_zod@3.24.2", + "@ai-sdk/provider", + "@ai-sdk/provider-utils", "zod", "zod-to-json-schema" ] @@ -480,396 +436,6 @@ "zod" ] }, - "@aws-crypto/crc32@5.2.0": { - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "dependencies": [ - "@aws-crypto/util", - "@aws-sdk/types", - "tslib" - ] - }, - "@aws-crypto/sha256-browser@5.2.0": { - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dependencies": [ - "@aws-crypto/sha256-js", - "@aws-crypto/supports-web-crypto", - "@aws-crypto/util", - "@aws-sdk/types", - "@aws-sdk/util-locate-window", - "@smithy/util-utf8@2.3.0", - "tslib" - ] - }, - "@aws-crypto/sha256-js@5.2.0": { - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dependencies": [ - "@aws-crypto/util", - "@aws-sdk/types", - "tslib" - ] - }, - "@aws-crypto/supports-web-crypto@5.2.0": { - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dependencies": [ - "tslib" - ] - }, - "@aws-crypto/util@5.2.0": { - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dependencies": [ - "@aws-sdk/types", - "@smithy/util-utf8@2.3.0", - "tslib" - ] - }, - "@aws-sdk/client-bedrock-runtime@3.758.0": { - "integrity": "sha512-T7s+fULUxN3AcJP+lgoUKLawzVEtyCTi+5Ga+wrHnqEPwAsM/wg7VctsZfow1fCgARLT/lzmP2LTCi8ycRnQWg==", - "dependencies": [ - "@aws-crypto/sha256-browser", - "@aws-crypto/sha256-js", - "@aws-sdk/core", - "@aws-sdk/credential-provider-node", - "@aws-sdk/middleware-host-header", - "@aws-sdk/middleware-logger", - "@aws-sdk/middleware-recursion-detection", - "@aws-sdk/middleware-user-agent", - "@aws-sdk/region-config-resolver", - "@aws-sdk/types", - "@aws-sdk/util-endpoints", - "@aws-sdk/util-user-agent-browser", - "@aws-sdk/util-user-agent-node", - "@smithy/config-resolver", - "@smithy/core", - "@smithy/eventstream-serde-browser", - "@smithy/eventstream-serde-config-resolver", - "@smithy/eventstream-serde-node", - "@smithy/fetch-http-handler", - "@smithy/hash-node", - "@smithy/invalid-dependency", - "@smithy/middleware-content-length", - "@smithy/middleware-endpoint", - "@smithy/middleware-retry", - "@smithy/middleware-serde", - "@smithy/middleware-stack", - "@smithy/node-config-provider", - "@smithy/node-http-handler", - "@smithy/protocol-http", - "@smithy/smithy-client", - "@smithy/types", - "@smithy/url-parser", - "@smithy/util-base64", - "@smithy/util-body-length-browser", - "@smithy/util-body-length-node", - "@smithy/util-defaults-mode-browser", - "@smithy/util-defaults-mode-node", - "@smithy/util-endpoints", - "@smithy/util-middleware", - "@smithy/util-retry", - "@smithy/util-stream", - "@smithy/util-utf8@4.0.0", - "@types/uuid", - "tslib", - "uuid" - ] - }, - "@aws-sdk/client-sso@3.758.0": { - "integrity": "sha512-BoGO6IIWrLyLxQG6txJw6RT2urmbtlwfggapNCrNPyYjlXpzTSJhBYjndg7TpDATFd0SXL0zm8y/tXsUXNkdYQ==", - "dependencies": [ - "@aws-crypto/sha256-browser", - "@aws-crypto/sha256-js", - "@aws-sdk/core", - "@aws-sdk/middleware-host-header", - "@aws-sdk/middleware-logger", - "@aws-sdk/middleware-recursion-detection", - "@aws-sdk/middleware-user-agent", - "@aws-sdk/region-config-resolver", - "@aws-sdk/types", - "@aws-sdk/util-endpoints", - "@aws-sdk/util-user-agent-browser", - "@aws-sdk/util-user-agent-node", - "@smithy/config-resolver", - "@smithy/core", - "@smithy/fetch-http-handler", - "@smithy/hash-node", - "@smithy/invalid-dependency", - "@smithy/middleware-content-length", - "@smithy/middleware-endpoint", - "@smithy/middleware-retry", - "@smithy/middleware-serde", - "@smithy/middleware-stack", - "@smithy/node-config-provider", - "@smithy/node-http-handler", - "@smithy/protocol-http", - "@smithy/smithy-client", - "@smithy/types", - "@smithy/url-parser", - "@smithy/util-base64", - "@smithy/util-body-length-browser", - "@smithy/util-body-length-node", - "@smithy/util-defaults-mode-browser", - "@smithy/util-defaults-mode-node", - "@smithy/util-endpoints", - "@smithy/util-middleware", - "@smithy/util-retry", - "@smithy/util-utf8@4.0.0", - "tslib" - ] - }, - "@aws-sdk/core@3.758.0": { - "integrity": "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg==", - "dependencies": [ - "@aws-sdk/types", - "@smithy/core", - "@smithy/node-config-provider", - "@smithy/property-provider", - "@smithy/protocol-http", - "@smithy/signature-v4", - "@smithy/smithy-client", - "@smithy/types", - "@smithy/util-middleware", - "fast-xml-parser", - "tslib" - ] - }, - "@aws-sdk/credential-provider-env@3.758.0": { - "integrity": "sha512-N27eFoRrO6MeUNumtNHDW9WOiwfd59LPXPqDrIa3kWL/s+fOKFHb9xIcF++bAwtcZnAxKkgpDCUP+INNZskE+w==", - "dependencies": [ - "@aws-sdk/core", - "@aws-sdk/types", - "@smithy/property-provider", - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/credential-provider-http@3.758.0": { - "integrity": "sha512-Xt9/U8qUCiw1hihztWkNeIR+arg6P+yda10OuCHX6kFVx3auTlU7+hCqs3UxqniGU4dguHuftf3mRpi5/GJ33Q==", - "dependencies": [ - "@aws-sdk/core", - "@aws-sdk/types", - "@smithy/fetch-http-handler", - "@smithy/node-http-handler", - "@smithy/property-provider", - "@smithy/protocol-http", - "@smithy/smithy-client", - "@smithy/types", - "@smithy/util-stream", - "tslib" - ] - }, - "@aws-sdk/credential-provider-ini@3.758.0": { - "integrity": "sha512-cymSKMcP5d+OsgetoIZ5QCe1wnp2Q/tq+uIxVdh9MbfdBBEnl9Ecq6dH6VlYS89sp4QKuxHxkWXVnbXU3Q19Aw==", - "dependencies": [ - "@aws-sdk/core", - "@aws-sdk/credential-provider-env", - "@aws-sdk/credential-provider-http", - "@aws-sdk/credential-provider-process", - "@aws-sdk/credential-provider-sso", - "@aws-sdk/credential-provider-web-identity", - "@aws-sdk/nested-clients", - "@aws-sdk/types", - "@smithy/credential-provider-imds", - "@smithy/property-provider", - "@smithy/shared-ini-file-loader", - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/credential-provider-node@3.758.0": { - "integrity": "sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ==", - "dependencies": [ - "@aws-sdk/credential-provider-env", - "@aws-sdk/credential-provider-http", - "@aws-sdk/credential-provider-ini", - "@aws-sdk/credential-provider-process", - "@aws-sdk/credential-provider-sso", - "@aws-sdk/credential-provider-web-identity", - "@aws-sdk/types", - "@smithy/credential-provider-imds", - "@smithy/property-provider", - "@smithy/shared-ini-file-loader", - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/credential-provider-process@3.758.0": { - "integrity": "sha512-AzcY74QTPqcbXWVgjpPZ3HOmxQZYPROIBz2YINF0OQk0MhezDWV/O7Xec+K1+MPGQO3qS6EDrUUlnPLjsqieHA==", - "dependencies": [ - "@aws-sdk/core", - "@aws-sdk/types", - "@smithy/property-provider", - "@smithy/shared-ini-file-loader", - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/credential-provider-sso@3.758.0": { - "integrity": "sha512-x0FYJqcOLUCv8GLLFDYMXRAQKGjoM+L0BG4BiHYZRDf24yQWFCAZsCQAYKo6XZYh2qznbsW6f//qpyJ5b0QVKQ==", - "dependencies": [ - "@aws-sdk/client-sso", - "@aws-sdk/core", - "@aws-sdk/token-providers", - "@aws-sdk/types", - "@smithy/property-provider", - "@smithy/shared-ini-file-loader", - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/credential-provider-web-identity@3.758.0": { - "integrity": "sha512-XGguXhBqiCXMXRxcfCAVPlMbm3VyJTou79r/3mxWddHWF0XbhaQiBIbUz6vobVTD25YQRbWSmSch7VA8kI5Lrw==", - "dependencies": [ - "@aws-sdk/core", - "@aws-sdk/nested-clients", - "@aws-sdk/types", - "@smithy/property-provider", - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/middleware-host-header@3.734.0": { - "integrity": "sha512-LW7RRgSOHHBzWZnigNsDIzu3AiwtjeI2X66v+Wn1P1u+eXssy1+up4ZY/h+t2sU4LU36UvEf+jrZti9c6vRnFw==", - "dependencies": [ - "@aws-sdk/types", - "@smithy/protocol-http", - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/middleware-logger@3.734.0": { - "integrity": "sha512-mUMFITpJUW3LcKvFok176eI5zXAUomVtahb9IQBwLzkqFYOrMJvWAvoV4yuxrJ8TlQBG8gyEnkb9SnhZvjg67w==", - "dependencies": [ - "@aws-sdk/types", - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/middleware-recursion-detection@3.734.0": { - "integrity": "sha512-CUat2d9ITsFc2XsmeiRQO96iWpxSKYFjxvj27Hc7vo87YUHRnfMfnc8jw1EpxEwMcvBD7LsRa6vDNky6AjcrFA==", - "dependencies": [ - "@aws-sdk/types", - "@smithy/protocol-http", - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/middleware-user-agent@3.758.0": { - "integrity": "sha512-iNyehQXtQlj69JCgfaOssgZD4HeYGOwxcaKeG6F+40cwBjTAi0+Ph1yfDwqk2qiBPIRWJ/9l2LodZbxiBqgrwg==", - "dependencies": [ - "@aws-sdk/core", - "@aws-sdk/types", - "@aws-sdk/util-endpoints", - "@smithy/core", - "@smithy/protocol-http", - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/nested-clients@3.758.0": { - "integrity": "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg==", - "dependencies": [ - "@aws-crypto/sha256-browser", - "@aws-crypto/sha256-js", - "@aws-sdk/core", - "@aws-sdk/middleware-host-header", - "@aws-sdk/middleware-logger", - "@aws-sdk/middleware-recursion-detection", - "@aws-sdk/middleware-user-agent", - "@aws-sdk/region-config-resolver", - "@aws-sdk/types", - "@aws-sdk/util-endpoints", - "@aws-sdk/util-user-agent-browser", - "@aws-sdk/util-user-agent-node", - "@smithy/config-resolver", - "@smithy/core", - "@smithy/fetch-http-handler", - "@smithy/hash-node", - "@smithy/invalid-dependency", - "@smithy/middleware-content-length", - "@smithy/middleware-endpoint", - "@smithy/middleware-retry", - "@smithy/middleware-serde", - "@smithy/middleware-stack", - "@smithy/node-config-provider", - "@smithy/node-http-handler", - "@smithy/protocol-http", - "@smithy/smithy-client", - "@smithy/types", - "@smithy/url-parser", - "@smithy/util-base64", - "@smithy/util-body-length-browser", - "@smithy/util-body-length-node", - "@smithy/util-defaults-mode-browser", - "@smithy/util-defaults-mode-node", - "@smithy/util-endpoints", - "@smithy/util-middleware", - "@smithy/util-retry", - "@smithy/util-utf8@4.0.0", - "tslib" - ] - }, - "@aws-sdk/region-config-resolver@3.734.0": { - "integrity": "sha512-Lvj1kPRC5IuJBr9DyJ9T9/plkh+EfKLy+12s/mykOy1JaKHDpvj+XGy2YO6YgYVOb8JFtaqloid+5COtje4JTQ==", - "dependencies": [ - "@aws-sdk/types", - "@smithy/node-config-provider", - "@smithy/types", - "@smithy/util-config-provider", - "@smithy/util-middleware", - "tslib" - ] - }, - "@aws-sdk/token-providers@3.758.0": { - "integrity": "sha512-ckptN1tNrIfQUaGWm/ayW1ddG+imbKN7HHhjFdS4VfItsP0QQOB0+Ov+tpgb4MoNR4JaUghMIVStjIeHN2ks1w==", - "dependencies": [ - "@aws-sdk/nested-clients", - "@aws-sdk/types", - "@smithy/property-provider", - "@smithy/shared-ini-file-loader", - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/types@3.734.0": { - "integrity": "sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg==", - "dependencies": [ - "@smithy/types", - "tslib" - ] - }, - "@aws-sdk/util-endpoints@3.743.0": { - "integrity": "sha512-sN1l559zrixeh5x+pttrnd0A3+r34r0tmPkJ/eaaMaAzXqsmKU/xYre9K3FNnsSS1J1k4PEfk/nHDTVUgFYjnw==", - "dependencies": [ - "@aws-sdk/types", - "@smithy/types", - "@smithy/util-endpoints", - "tslib" - ] - }, - "@aws-sdk/util-locate-window@3.723.0": { - "integrity": "sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==", - "dependencies": [ - "tslib" - ] - }, - "@aws-sdk/util-user-agent-browser@3.734.0": { - "integrity": "sha512-xQTCus6Q9LwUuALW+S76OL0jcWtMOVu14q+GoLnWPUM7QeUw963oQcLhF7oq0CtaLLKyl4GOUfcwc773Zmwwng==", - "dependencies": [ - "@aws-sdk/types", - "@smithy/types", - "bowser", - "tslib" - ] - }, - "@aws-sdk/util-user-agent-node@3.758.0": { - "integrity": "sha512-A5EZw85V6WhoKMV2hbuFRvb9NPlxEErb4HPO6/SPXYY4QrjprIzScHxikqcWv1w4J3apB1wto9LPU3IMsYtfrw==", - "dependencies": [ - "@aws-sdk/middleware-user-agent", - "@aws-sdk/types", - "@smithy/node-config-provider", - "@smithy/types", - "tslib" - ] - }, "@babel/code-frame@7.26.2": { "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dependencies": [ @@ -2269,397 +1835,6 @@ "qr-creator" ] }, - "@smithy/abort-controller@4.0.1": { - "integrity": "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==", - "dependencies": [ - "@smithy/types", - "tslib" - ] - }, - "@smithy/config-resolver@4.0.1": { - "integrity": "sha512-Igfg8lKu3dRVkTSEm98QpZUvKEOa71jDX4vKRcvJVyRc3UgN3j7vFMf0s7xLQhYmKa8kyJGQgUJDOV5V3neVlQ==", - "dependencies": [ - "@smithy/node-config-provider", - "@smithy/types", - "@smithy/util-config-provider", - "@smithy/util-middleware", - "tslib" - ] - }, - "@smithy/core@3.1.5": { - "integrity": "sha512-HLclGWPkCsekQgsyzxLhCQLa8THWXtB5PxyYN+2O6nkyLt550KQKTlbV2D1/j5dNIQapAZM1+qFnpBFxZQkgCA==", - "dependencies": [ - "@smithy/middleware-serde", - "@smithy/protocol-http", - "@smithy/types", - "@smithy/util-body-length-browser", - "@smithy/util-middleware", - "@smithy/util-stream", - "@smithy/util-utf8@4.0.0", - "tslib" - ] - }, - "@smithy/credential-provider-imds@4.0.1": { - "integrity": "sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg==", - "dependencies": [ - "@smithy/node-config-provider", - "@smithy/property-provider", - "@smithy/types", - "@smithy/url-parser", - "tslib" - ] - }, - "@smithy/eventstream-codec@4.0.1": { - "integrity": "sha512-Q2bCAAR6zXNVtJgifsU16ZjKGqdw/DyecKNgIgi7dlqw04fqDu0mnq+JmGphqheypVc64CYq3azSuCpAdFk2+A==", - "dependencies": [ - "@aws-crypto/crc32", - "@smithy/types", - "@smithy/util-hex-encoding", - "tslib" - ] - }, - "@smithy/eventstream-serde-browser@4.0.1": { - "integrity": "sha512-HbIybmz5rhNg+zxKiyVAnvdM3vkzjE6ccrJ620iPL8IXcJEntd3hnBl+ktMwIy12Te/kyrSbUb8UCdnUT4QEdA==", - "dependencies": [ - "@smithy/eventstream-serde-universal", - "@smithy/types", - "tslib" - ] - }, - "@smithy/eventstream-serde-config-resolver@4.0.1": { - "integrity": "sha512-lSipaiq3rmHguHa3QFF4YcCM3VJOrY9oq2sow3qlhFY+nBSTF/nrO82MUQRPrxHQXA58J5G1UnU2WuJfi465BA==", - "dependencies": [ - "@smithy/types", - "tslib" - ] - }, - "@smithy/eventstream-serde-node@4.0.1": { - "integrity": "sha512-o4CoOI6oYGYJ4zXo34U8X9szDe3oGjmHgsMGiZM0j4vtNoT+h80TLnkUcrLZR3+E6HIxqW+G+9WHAVfl0GXK0Q==", - "dependencies": [ - "@smithy/eventstream-serde-universal", - "@smithy/types", - "tslib" - ] - }, - "@smithy/eventstream-serde-universal@4.0.1": { - "integrity": "sha512-Z94uZp0tGJuxds3iEAZBqGU2QiaBHP4YytLUjwZWx+oUeohCsLyUm33yp4MMBmhkuPqSbQCXq5hDet6JGUgHWA==", - "dependencies": [ - "@smithy/eventstream-codec", - "@smithy/types", - "tslib" - ] - }, - "@smithy/fetch-http-handler@5.0.1": { - "integrity": "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA==", - "dependencies": [ - "@smithy/protocol-http", - "@smithy/querystring-builder", - "@smithy/types", - "@smithy/util-base64", - "tslib" - ] - }, - "@smithy/hash-node@4.0.1": { - "integrity": "sha512-TJ6oZS+3r2Xu4emVse1YPB3Dq3d8RkZDKcPr71Nj/lJsdAP1c7oFzYqEn1IBc915TsgLl2xIJNuxCz+gLbLE0w==", - "dependencies": [ - "@smithy/types", - "@smithy/util-buffer-from@4.0.0", - "@smithy/util-utf8@4.0.0", - "tslib" - ] - }, - "@smithy/invalid-dependency@4.0.1": { - "integrity": "sha512-gdudFPf4QRQ5pzj7HEnu6FhKRi61BfH/Gk5Yf6O0KiSbr1LlVhgjThcvjdu658VE6Nve8vaIWB8/fodmS1rBPQ==", - "dependencies": [ - "@smithy/types", - "tslib" - ] - }, - "@smithy/is-array-buffer@2.2.0": { - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dependencies": [ - "tslib" - ] - }, - "@smithy/is-array-buffer@4.0.0": { - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", - "dependencies": [ - "tslib" - ] - }, - "@smithy/middleware-content-length@4.0.1": { - "integrity": "sha512-OGXo7w5EkB5pPiac7KNzVtfCW2vKBTZNuCctn++TTSOMpe6RZO/n6WEC1AxJINn3+vWLKW49uad3lo/u0WJ9oQ==", - "dependencies": [ - "@smithy/protocol-http", - "@smithy/types", - "tslib" - ] - }, - "@smithy/middleware-endpoint@4.0.6": { - "integrity": "sha512-ftpmkTHIFqgaFugcjzLZv3kzPEFsBFSnq1JsIkr2mwFzCraZVhQk2gqN51OOeRxqhbPTkRFj39Qd2V91E/mQxg==", - "dependencies": [ - "@smithy/core", - "@smithy/middleware-serde", - "@smithy/node-config-provider", - "@smithy/shared-ini-file-loader", - "@smithy/types", - "@smithy/url-parser", - "@smithy/util-middleware", - "tslib" - ] - }, - "@smithy/middleware-retry@4.0.7": { - "integrity": "sha512-58j9XbUPLkqAcV1kHzVX/kAR16GT+j7DUZJqwzsxh1jtz7G82caZiGyyFgUvogVfNTg3TeAOIJepGc8TXF4AVQ==", - "dependencies": [ - "@smithy/node-config-provider", - "@smithy/protocol-http", - "@smithy/service-error-classification", - "@smithy/smithy-client", - "@smithy/types", - "@smithy/util-middleware", - "@smithy/util-retry", - "tslib", - "uuid" - ] - }, - "@smithy/middleware-serde@4.0.2": { - "integrity": "sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ==", - "dependencies": [ - "@smithy/types", - "tslib" - ] - }, - "@smithy/middleware-stack@4.0.1": { - "integrity": "sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA==", - "dependencies": [ - "@smithy/types", - "tslib" - ] - }, - "@smithy/node-config-provider@4.0.1": { - "integrity": "sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ==", - "dependencies": [ - "@smithy/property-provider", - "@smithy/shared-ini-file-loader", - "@smithy/types", - "tslib" - ] - }, - "@smithy/node-http-handler@4.0.3": { - "integrity": "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA==", - "dependencies": [ - "@smithy/abort-controller", - "@smithy/protocol-http", - "@smithy/querystring-builder", - "@smithy/types", - "tslib" - ] - }, - "@smithy/property-provider@4.0.1": { - "integrity": "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ==", - "dependencies": [ - "@smithy/types", - "tslib" - ] - }, - "@smithy/protocol-http@5.0.1": { - "integrity": "sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ==", - "dependencies": [ - "@smithy/types", - "tslib" - ] - }, - "@smithy/querystring-builder@4.0.1": { - "integrity": "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg==", - "dependencies": [ - "@smithy/types", - "@smithy/util-uri-escape", - "tslib" - ] - }, - "@smithy/querystring-parser@4.0.1": { - "integrity": "sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw==", - "dependencies": [ - "@smithy/types", - "tslib" - ] - }, - "@smithy/service-error-classification@4.0.1": { - "integrity": "sha512-3JNjBfOWpj/mYfjXJHB4Txc/7E4LVq32bwzE7m28GN79+M1f76XHflUaSUkhOriprPDzev9cX/M+dEB80DNDKA==", - "dependencies": [ - "@smithy/types" - ] - }, - "@smithy/shared-ini-file-loader@4.0.1": { - "integrity": "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw==", - "dependencies": [ - "@smithy/types", - "tslib" - ] - }, - "@smithy/signature-v4@5.0.1": { - "integrity": "sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA==", - "dependencies": [ - "@smithy/is-array-buffer@4.0.0", - "@smithy/protocol-http", - "@smithy/types", - "@smithy/util-hex-encoding", - "@smithy/util-middleware", - "@smithy/util-uri-escape", - "@smithy/util-utf8@4.0.0", - "tslib" - ] - }, - "@smithy/smithy-client@4.1.6": { - "integrity": "sha512-UYDolNg6h2O0L+cJjtgSyKKvEKCOa/8FHYJnBobyeoeWDmNpXjwOAtw16ezyeu1ETuuLEOZbrynK0ZY1Lx9Jbw==", - "dependencies": [ - "@smithy/core", - "@smithy/middleware-endpoint", - "@smithy/middleware-stack", - "@smithy/protocol-http", - "@smithy/types", - "@smithy/util-stream", - "tslib" - ] - }, - "@smithy/types@4.1.0": { - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "dependencies": [ - "tslib" - ] - }, - "@smithy/url-parser@4.0.1": { - "integrity": "sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g==", - "dependencies": [ - "@smithy/querystring-parser", - "@smithy/types", - "tslib" - ] - }, - "@smithy/util-base64@4.0.0": { - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "dependencies": [ - "@smithy/util-buffer-from@4.0.0", - "@smithy/util-utf8@4.0.0", - "tslib" - ] - }, - "@smithy/util-body-length-browser@4.0.0": { - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", - "dependencies": [ - "tslib" - ] - }, - "@smithy/util-body-length-node@4.0.0": { - "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", - "dependencies": [ - "tslib" - ] - }, - "@smithy/util-buffer-from@2.2.0": { - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dependencies": [ - "@smithy/is-array-buffer@2.2.0", - "tslib" - ] - }, - "@smithy/util-buffer-from@4.0.0": { - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", - "dependencies": [ - "@smithy/is-array-buffer@4.0.0", - "tslib" - ] - }, - "@smithy/util-config-provider@4.0.0": { - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", - "dependencies": [ - "tslib" - ] - }, - "@smithy/util-defaults-mode-browser@4.0.7": { - "integrity": "sha512-CZgDDrYHLv0RUElOsmZtAnp1pIjwDVCSuZWOPhIOBvG36RDfX1Q9+6lS61xBf+qqvHoqRjHxgINeQz47cYFC2Q==", - "dependencies": [ - "@smithy/property-provider", - "@smithy/smithy-client", - "@smithy/types", - "bowser", - "tslib" - ] - }, - "@smithy/util-defaults-mode-node@4.0.7": { - "integrity": "sha512-79fQW3hnfCdrfIi1soPbK3zmooRFnLpSx3Vxi6nUlqaaQeC5dm8plt4OTNDNqEEEDkvKghZSaoti684dQFVrGQ==", - "dependencies": [ - "@smithy/config-resolver", - "@smithy/credential-provider-imds", - "@smithy/node-config-provider", - "@smithy/property-provider", - "@smithy/smithy-client", - "@smithy/types", - "tslib" - ] - }, - "@smithy/util-endpoints@3.0.1": { - "integrity": "sha512-zVdUENQpdtn9jbpD9SCFK4+aSiavRb9BxEtw9ZGUR1TYo6bBHbIoi7VkrFQ0/RwZlzx0wRBaRmPclj8iAoJCLA==", - "dependencies": [ - "@smithy/node-config-provider", - "@smithy/types", - "tslib" - ] - }, - "@smithy/util-hex-encoding@4.0.0": { - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", - "dependencies": [ - "tslib" - ] - }, - "@smithy/util-middleware@4.0.1": { - "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", - "dependencies": [ - "@smithy/types", - "tslib" - ] - }, - "@smithy/util-retry@4.0.1": { - "integrity": "sha512-WmRHqNVwn3kI3rKk1LsKcVgPBG6iLTBGC1iYOV3GQegwJ3E8yjzHytPt26VNzOWr1qu0xE03nK0Ug8S7T7oufw==", - "dependencies": [ - "@smithy/service-error-classification", - "@smithy/types", - "tslib" - ] - }, - "@smithy/util-stream@4.1.2": { - "integrity": "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw==", - "dependencies": [ - "@smithy/fetch-http-handler", - "@smithy/node-http-handler", - "@smithy/types", - "@smithy/util-base64", - "@smithy/util-buffer-from@4.0.0", - "@smithy/util-hex-encoding", - "@smithy/util-utf8@4.0.0", - "tslib" - ] - }, - "@smithy/util-uri-escape@4.0.0": { - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", - "dependencies": [ - "tslib" - ] - }, - "@smithy/util-utf8@2.3.0": { - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dependencies": [ - "@smithy/util-buffer-from@2.2.0", - "tslib" - ] - }, - "@smithy/util-utf8@4.0.0": { - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", - "dependencies": [ - "@smithy/util-buffer-from@4.0.0", - "tslib" - ] - }, "@tailwindcss/node@4.0.13": { "integrity": "sha512-P9TmtE9Vew0vv5FwyD4bsg/dHHsIsAuUXkenuGUc5gm8fYgaxpdoxIKngCyEMEQxyCKR8PQY5V5VrrKNOx7exg==", "dependencies": [ @@ -3044,9 +2219,6 @@ "@types/unist@3.0.3": { "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, - "@types/uuid@9.0.8": { - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==" - }, "@types/webxr@0.5.21": { "integrity": "sha512-geZIAtLzjGmgY2JUi6VxXdCrTb99A7yP49lxLr2Nm/uIK0PkkxcEi4OGhoGDO4pxCf3JwGz2GiJL2Ej4K2bKaA==" }, @@ -3397,8 +2569,8 @@ "ai@4.1.54_react@18.3.1_zod@3.24.2": { "integrity": "sha512-VcUZhNEC9i1OpdhDaz1cF0IllgMqhwoUdqHQT1U3dKvS9KnOa9qvEtUUAilA+VHI/1LSZF4VzGhXPC7QMT9NMg==", "dependencies": [ - "@ai-sdk/provider@1.0.10", - "@ai-sdk/provider-utils@2.1.11_zod@3.24.2", + "@ai-sdk/provider", + "@ai-sdk/provider-utils", "@ai-sdk/react", "@ai-sdk/ui-utils", "@opentelemetry/api", @@ -3530,9 +2702,6 @@ "bignumber.js@9.1.2": { "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" }, - "bowser@2.11.0": { - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" - }, "brace-expansion@2.0.1": { "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": [ @@ -4238,12 +3407,6 @@ "fast-safe-stringify@2.1.1": { "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, - "fast-xml-parser@4.4.1": { - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", - "dependencies": [ - "strnum" - ] - }, "fastq@1.19.1": { "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dependencies": [ @@ -6326,9 +5489,6 @@ "strip-json-comments@3.1.1": { "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, - "strnum@1.1.2": { - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==" - }, "style-mod@4.1.2": { "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" }, @@ -7082,9 +6242,7 @@ "toolshed": { "dependencies": [ "jsr:@std/cli@^1.0.12", - "npm:@ai-sdk/amazon-bedrock@^1.1.6", "npm:@ai-sdk/anthropic@^1.1.6", - "npm:@ai-sdk/cerebras@~0.1.8", "npm:@ai-sdk/google-vertex@^2.1.12", "npm:@ai-sdk/groq@^1.1.7", "npm:@ai-sdk/openai@^1.1.9", diff --git a/jumble/integration/basic-flow.test.ts b/jumble/integration/basic-flow.test.ts index bcf81cc0f..66f2ca8e2 100644 --- a/jumble/integration/basic-flow.test.ts +++ b/jumble/integration/basic-flow.test.ts @@ -216,7 +216,7 @@ Deno.test({ await sleep(1000); await page.keyboard.press("Enter"); - await sleep(1000); + await sleep(300); await page.keyboard.type("count of values"); await sleep(1000); await page.keyboard.press("Enter"); diff --git a/jumble/integration/cache/llm-api-cache/61063134e61d3f147733a0cf9711b6fa9cf9f2d5acf81bfeccf71285c0ed93f9.json b/jumble/integration/cache/llm-api-cache/61063134e61d3f147733a0cf9711b6fa9cf9f2d5acf81bfeccf71285c0ed93f9.json new file mode 100644 index 000000000..d22b0d3c4 --- /dev/null +++ b/jumble/integration/cache/llm-api-cache/61063134e61d3f147733a0cf9711b6fa9cf9f2d5acf81bfeccf71285c0ed93f9.json @@ -0,0 +1,20 @@ +{ + "model": "anthropic:claude-3-7-sonnet-latest", + "system": "# React Component Builder\n\nCreate an interactive React component that fulfills the user's request. Focus on delivering a clean, useful implementation with appropriate features.\n\n## You Are Part of a Two-Phase Process\n\n1. First phase (already completed):\n - Analyzed the user's request\n - Created a detailed specification\n - Generated a structured data schema\n\n2. Your job (second phase):\n - Create a reactive UI component based on the provided specification and schema\n - Implement the UI exactly according to the specification\n - Strictly adhere to the data schema provided\n\n## Required Elements\n- Define a title with `const title = 'Your App Name';`\n- Implement both `onLoad` and `onReady` functions\n- Use Tailwind CSS for styling with tasteful defaults\n- Do not write inline, use emoji for icons\n- Carefully avoid infinite loops and recursion that may cause performance issues\n\n## Code Structure\n1. React and ReactDOM are pre-imported - don't import them again\n2. All React hooks must be namespaced (e.g., `React.useState`, `React.useEffect`)\n3. Follow React hooks rules - never nest or conditionally call hooks\n4. For form handling, use `onClick` handlers instead of `onSubmit`\n\n## Available APIs\n- **useDoc(key, defaultValue)** - Persistent data storage with reactive updates\n- **llm(promptPayload)** - Send requests to the language model\n- **readWebpage(url)** - Fetch and parse external web content\n- **generateImage(prompt)** - Create AI-generated images\n\n## Important Note About useDoc\n- **useDoc is a React Hook** and must follow all React hook rules\n- It should only be used for persistent state and must draw from the provided schema\n - For any ephemeral state, use `React.useState`\n- Only call useDoc at the top level of your function components or custom hooks\n- Do not call useDoc inside loops, conditions, or nested functions\n- useDoc cannot be used outside of `onReady` components - it must be called during rendering\n\n## Library Usage\n- Request additional libraries in `onLoad` by returning an array of module names\n- Available libraries:\n - imports : [object Object]\n- Only use the explicitly provided libraries\n\n## Security Restrictions\n- Do not use browser dialog functions (`prompt()`, `alert()`, `confirm()`)\n- Avoid any methods that could compromise security or user experience\n\n\n\n{\n \"type\": \"object\",\n \"properties\": {\n \"simpleValue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"counts\": {\n \"type\": \"object\",\n \"title\": \"Value Counts\",\n \"description\": \"Map of values to their occurrence counts\",\n \"additionalProperties\": {\n \"type\": \"integer\",\n \"minimum\": 1\n }\n },\n \"total\": {\n \"type\": \"integer\",\n \"title\": \"Total Count\",\n \"description\": \"Total number of values counted\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"uniqueCount\": {\n \"type\": \"integer\",\n \"title\": \"Unique Count\",\n \"description\": \"Number of unique values found\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"source\": {\n \"type\": \"string\",\n \"title\": \"Source\",\n \"description\": \"Identifier of the source array that was counted\",\n \"default\": \"simpleValue2.values\"\n }\n },\n \"title\": \"Value Counter\",\n \"description\": \"A utility to count occurrences of values in an array.\"\n}\n\n\n\n# SDK Usage Guide\n\n## 1. `useDoc` Hook\n\nThe `useDoc` hook binds to a reactive cell given key and returns a tuple `[doc, setDoc]`:\n\nAny keys from the view-model-schema are valid for useDoc, any other keys will fail. Provide a default as the second argument, **do not set an initial value explicitly**.\n\nFor this schema:\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"counter\": {\n \"type\": \"number\",\n },\n \"title\": {\n \"type\": \"string\",\n \"default\": \"My Counter App\"\n }\n }\n}\n```\n\n```jsx\nfunction CounterComponent() {\n // Correct: useDoc called at top level of component\n const [counter, setCounter] = useDoc(\"counter\", -1); // default\n\n // Incorrect: would cause errors\n // if(something) {\n // const [data, setData] = useDoc(\"data\", {}); // Never do this!\n // }\n\n const onIncrement = useCallback(() => {\n // writing to the cell automatically triggers a re-render\n setCounter(counter + 1);\n }, [counter]);\n\n return (\n \n );\n}\n```\n\n## 2. llm Function\n\n```jsx\nasync function fetchLLMResponse() {\n // place user-level requirements in system prompt\n const promptPayload = {\n system: 'Translate all the messages to emojis, reply in JSON.',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke']\n };\n const result = await llm(promptPayload)\n console.log('LLM responded:', result);\n}\n```\n\nIf you need JSON to be returned from the LLM, you can enable the `mode: 'json'` in the `promptPayload`.\n\n```jsx\nconst promptPayload = {\n system: 'Translate all the messages to emojis, reply in JSON.',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke'],\n mode: 'json'\n};\nconst result = await llm(promptPayload);\nconsole.log('JSON response from llm:', result);\n```\n\n## 3. readWebpage Function\n\n```jsx\nasync function fetchFromUrl() {\n const url = 'https://twopm.studio';\n const result = await readWebpage(url);\n console.log('Markdown:', result.content);\n}\n```\n\n## 4. generateImage Function\n\n```jsx\nfunction ImageComponent() {\n return \"Generated;\n}\n\n```\n## 5. Using the Interface Functions\n\n```javascript\n// Import from modern ESM libraries:\n// - react\n// - react-dom\n// - react-dom/client\n// - d3\n// - moment\n// - marked\n// - @react-spring/web\n// - @use-gesture/react\n// - uuid\n// - tone\nfunction onLoad() {\n return ['@react-spring/web']; // Request the modules you need\n}\n\nconst title = 'My ESM App';\nfunction ImageComponent({ url }) {\n return \"Generated;\n}\n\nfunction MyComponent({ label, description }) {\n return (\n
\n

{label}

\n

{description}

\n \n
\n );\n}\n\nfunction TodoItem({ todo, onToggle, onDelete }) {\n return (\n
\n \n \n {todo.text}\n \n \n Delete\n \n
\n );\n}\n\nfunction TodoList({ todo, setTodos}) {\n const [newTodo, setNewTodo] = React.useState('');\n\n const addTodo = () => {\n if (newTodo.trim() === '') return;\n\n const newTodoItem = {\n id: Date.now(),\n text: newTodo,\n completed: false\n };\n\n setTodos([...todos, newTodoItem]);\n setNewTodo('');\n };\n\n const toggleTodo = (id) => {\n setTodos(todos.map(todo =>\n todo.id === id ? { ...todo, completed: !todo.completed } : todo\n ));\n };\n\n const deleteTodo = (id) => {\n setTodos(todos.filter(todo => todo.id !== id));\n };\n\n return (\n
\n

Todo List

\n\n
\n setNewTodo(e.target.value)}\n placeholder=\"Add a new todo\"\n className=\"flex-grow p-2 border rounded-l\"\n />\n \n Add\n \n
\n\n
\n {todos.length > 0 ? (\n todos.map(todo => (\n toggleTodo(todo.id)}\n onDelete={() => deleteTodo(todo.id)}\n />\n ))\n ) : (\n

No todos yet!

\n )}\n
\n
\n );\n}\n\n// Main application code with modules passed as third parameter\nfunction onReady(mount, sourceData, libs) {\n const { useState, useEffect } = React; // React is available globally\n const { useSpring, animated } = libs['@react-spring/web']; // Access imported module\n\n function MyApp() {\n const [count, setCount] = useDoc('count', 0);\n const [todos, setTodos] = useDoc('todos', [\n { id: 1, text: 'Learn React', completed: false },\n { id: 2, text: 'Build a Todo App', completed: false }\n ]);\n const props = useSpring({\n from: { opacity: 0 },\n to: { opacity: 1 }\n });\n\n return (\n
\n \n setCount(count + 1)}\n >\n Clicks: {count}\n \n \n \n
\n );\n }\n\n // Use the client API for React 18\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```\n\n", + "messages": [ + { + "role": "user", + "content": "The user asked you to create the source code with the following specification:\n```\ncount of values\n1. Extract the \"values\" array from the simpleValue2 object in the existing schema\n2. Process the array to count occurrences of each unique string value\n3. Return a result object with the counts mapped by value\nThe Value Counter will analyze the \"values\" array within the simpleValue2 object and count the occurrences of each unique string. It will return a summary object containing each unique value as a key and its count as the corresponding value. This simple counter will maintain the existing schema structure while providing useful aggregation functionality.\n```" + }, + { + "role": "assistant", + "content": "\nconst title = 'Value Counter';\n\nfunction onLoad() {\n return []; // No additional libraries needed\n}\n\nfunction onReady(mountNode) {\n function ValueCounter() {\n // Get the values array from simpleValue2\n const [simpleValue2, setSimpleValue2] = useDoc('simpleValue2', { values: [] });\n // Get the counts object that maps values to their occurrence counts\n const [counts, setCounts] = useDoc('counts', {});\n // Get the total count of all values\n const [total, setTotal] = useDoc('total', 0);\n // Get the count of unique values\n const [uniqueCount, setUniqueCount] = useDoc('uniqueCount', 0);\n // Get the source identifier\n const [source, setSource] = useDoc('source', 'simpleValue2.values');\n \n // Input state for adding new values\n const [newValue, setNewValue] = React.useState('');\n \n // Function to count occurrences in the values array\n const countValues = React.useCallback(() => {\n const { values } = simpleValue2;\n if (!Array.isArray(values)) return;\n \n const newCounts = {};\n let newTotal = 0;\n \n // Count occurrences of each value\n values.forEach(value => {\n if (typeof value === 'string') {\n newCounts[value] = (newCounts[value] || 0) + 1;\n newTotal++;\n }\n });\n \n // Update the counts, total, and uniqueCount\n setCounts(newCounts);\n setTotal(newTotal);\n setUniqueCount(Object.keys(newCounts).length);\n }, [simpleValue2, setCounts, setTotal, setUniqueCount]);\n \n // Count values when the component mounts or when values change\n React.useEffect(() => {\n countValues();\n }, [simpleValue2, countValues]);\n \n // Function to add a new value to the array\n const handleAddValue = () => {\n if (newValue.trim() === '') return;\n \n const updatedValues = [...(simpleValue2.values || []), newValue.trim()];\n setSimpleValue2({ ...simpleValue2, values: updatedValues });\n setNewValue('');\n };\n \n // Function to remove a value from the array\n const handleRemoveValue = (valueToRemove) => {\n const updatedValues = (simpleValue2.values || []).filter(\n (_, index) => index !== valueToRemove\n );\n setSimpleValue2({ ...simpleValue2, values: updatedValues });\n };\n \n // Function to clear all values\n const handleClearValues = () => {\n setSimpleValue2({ ...simpleValue2, values: [] });\n };\n \n return (\n
\n

{title}

\n \n {/* Input form to add new values */}\n
\n
\n setNewValue(e.target.value)}\n placeholder=\"Enter a value\"\n className=\"flex-grow p-2 border border-gray-300 rounded-l focus:outline-none focus:ring-2 focus:ring-blue-500\"\n />\n \n Add Value\n \n
\n
\n \n {/* Display current values */}\n
\n

Current Values

\n {simpleValue2.values && simpleValue2.values.length > 0 ? (\n
\n
    \n {simpleValue2.values.map((value, index) => (\n
  • \n {value}\n handleRemoveValue(index)}\n className=\"text-red-500 hover:text-red-700\"\n >\n 🗑️\n \n
  • \n ))}\n
\n
\n \n Clear All\n \n
\n
\n ) : (\n

No values added yet.

\n )}\n
\n \n {/* Display counts */}\n
\n

Value Counts

\n
\n
\n
\n

Total Values

\n

{total}

\n
\n
\n

Unique Values

\n

{uniqueCount}

\n
\n
\n \n {Object.keys(counts).length > 0 ? (\n
\n

Breakdown

\n
\n \n \n \n \n \n \n \n \n \n {Object.entries(counts).map(([value, count]) => (\n \n \n \n \n \n ))}\n \n
ValueCountPercentage
{value}{count}\n {total > 0 ? `${((count / total) * 100).toFixed(1)}%` : '0%'}\n
\n
\n
\n ) : (\n

No data to display.

\n )}\n
\n
\n \n
\n Source: {source}\n
\n
\n );\n }\n \n ReactDOM.createRoot(mountNode).render();\n}\n```" + }, + { + "role": "assistant", + "content": "\nconst title = 'Value Counter';\n\nfunction onLoad() {\n return []; // No additional libraries needed\n}\n\nfunction onReady(mountNode) {\n function ValueCounter() {\n // Get the values array from simpleValue2\n const [simpleValue2, setSimpleValue2] = useDoc('simpleValue2', { values: [] });\n // Get the counts object that maps values to their occurrence counts\n const [counts, setCounts] = useDoc('counts', {});\n // Get the total count of all values\n const [total, setTotal] = useDoc('total', 0);\n // Get the count of unique values\n const [uniqueCount, setUniqueCount] = useDoc('uniqueCount', 0);\n // Get the source identifier\n const [source, setSource] = useDoc('source', 'simpleValue2.values');\n \n // Input state for adding new values\n const [newValue, setNewValue] = React.useState('');\n \n // Function to count occurrences in the values array\n const countValues = React.useCallback(() => {\n const { values } = simpleValue2;\n if (!Array.isArray(values)) return;\n \n const newCounts = {};\n let newTotal = 0;\n \n // Count occurrences of each value\n values.forEach(value => {\n if (typeof value === 'string') {\n newCounts[value] = (newCounts[value] || 0) + 1;\n newTotal++;\n }\n });\n \n // Update the counts, total, and uniqueCount\n setCounts(newCounts);\n setTotal(newTotal);\n setUniqueCount(Object.keys(newCounts).length);\n }, [simpleValue2, setCounts, setTotal, setUniqueCount]);\n \n // Count values when the component mounts or when values change\n React.useEffect(() => {\n countValues();\n }, [simpleValue2, countValues]);\n \n // Function to add a new value to the array\n const handleAddValue = () => {\n if (newValue.trim() === '') return;\n \n const updatedValues = [...(simpleValue2.values || []), newValue.trim()];\n setSimpleValue2({ ...simpleValue2, values: updatedValues });\n setNewValue('');\n };\n \n // Function to remove a value from the array\n const handleRemoveValue = (valueToRemove) => {\n const updatedValues = (simpleValue2.values || []).filter(\n (_, index) => index !== valueToRemove\n );\n setSimpleValue2({ ...simpleValue2, values: updatedValues });\n };\n \n // Function to clear all values\n const handleClearValues = () => {\n setSimpleValue2({ ...simpleValue2, values: [] });\n };\n \n return (\n
\n

{title}

\n \n {/* Input form to add new values */}\n
\n
\n setNewValue(e.target.value)}\n placeholder=\"Enter a value\"\n className=\"flex-grow p-2 border border-gray-300 rounded-l focus:outline-none focus:ring-2 focus:ring-blue-500\"\n />\n \n Add Value\n \n
\n
\n \n {/* Display current values */}\n
\n

Current Values

\n {simpleValue2.values && simpleValue2.values.length > 0 ? (\n
\n
    \n {simpleValue2.values.map((value, index) => (\n
  • \n {value}\n handleRemoveValue(index)}\n className=\"text-red-500 hover:text-red-700\"\n >\n 🗑️\n \n
  • \n ))}\n
\n
\n \n Clear All\n \n
\n
\n ) : (\n

No values added yet.

\n )}\n
\n \n {/* Display counts */}\n
\n

Value Counts

\n
\n
\n
\n

Total Values

\n

{total}

\n
\n
\n

Unique Values

\n

{uniqueCount}

\n
\n
\n \n {Object.keys(counts).length > 0 ? (\n
\n

Breakdown

\n
\n \n \n \n \n \n \n \n \n \n {Object.entries(counts).map(([value, count]) => (\n \n \n \n \n \n ))}\n \n
ValueCountPercentage
{value}{count}\n {total > 0 ? `${((count / total) * 100).toFixed(1)}%` : '0%'}\n
\n
\n
\n ) : (\n

No data to display.

\n )}\n
\n
\n \n
\n Source: {source}\n
\n
\n );\n }\n \n ReactDOM.createRoot(mountNode).render();\n}\n```" + } + ], + "stop": "\n```", + "stream": false +} \ No newline at end of file diff --git a/jumble/integration/cache/llm-api-cache/b3fc0f350fc478943cc9c8b7b227221231dac12cd20e0af4e5d3e89a5919daeb.json b/jumble/integration/cache/llm-api-cache/b3fc0f350fc478943cc9c8b7b227221231dac12cd20e0af4e5d3e89a5919daeb.json deleted file mode 100644 index a489cd05e..000000000 --- a/jumble/integration/cache/llm-api-cache/b3fc0f350fc478943cc9c8b7b227221231dac12cd20e0af4e5d3e89a5919daeb.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "model": "anthropic:claude-3-7-sonnet-latest", - "system": "# React Component Builder\n\nCreate an interactive React component that fulfills the user's request. Focus on delivering a clean, useful implementation with appropriate features.\n\n## You Are Part of a Two-Phase Process\n\n1. First phase (already completed):\n - Analyzed the user's request\n - Created a detailed specification\n - Generated a structured data schema\n\n2. Your job (second phase):\n - Create a reactive UI component based on the provided specification and schema\n - Implement the UI exactly according to the specification\n - Strictly adhere to the data schema provided\n\n## Required Elements\n- Define a title with `const title = 'Your App Name';`\n- Implement both `onLoad` and `onReady` functions\n- Use Tailwind CSS for styling with tasteful defaults\n- Do not write inline, use emoji for icons\n- Carefully avoid infinite loops and recursion that may cause performance issues\n\n## Code Structure\n1. React and ReactDOM are pre-imported - don't import them again\n2. All React hooks must be namespaced (e.g., `React.useState`, `React.useEffect`)\n3. Follow React hooks rules - never nest or conditionally call hooks\n4. For form handling, use `onClick` handlers instead of `onSubmit`\n\n## Available APIs\n- **useDoc(key, defaultValue)** - Persistent data storage with reactive updates\n- **llm(promptPayload)** - Send requests to the language model\n- **readWebpage(url)** - Fetch and parse external web content\n- **generateImage(prompt)** - Create AI-generated images\n\n## Important Note About useDoc\n- **useDoc is a React Hook** and must follow all React hook rules\n- It should only be used for persistent state and must draw from the provided schema\n - For any ephemeral state, use `React.useState`\n- Only call useDoc at the top level of your function components or custom hooks\n- Do not call useDoc inside loops, conditions, or nested functions\n- useDoc cannot be used outside of `onReady` components - it must be called during rendering\n\n## Library Usage\n- Request additional libraries in `onLoad` by returning an array of module names\n- Available libraries:\n - imports : [object Object]\n- Only use the explicitly provided libraries\n\n## Security Restrictions\n- Do not use browser dialog functions (`prompt()`, `alert()`, `confirm()`)\n- Avoid any methods that could compromise security or user experience\n\n\n\n{\n \"type\": \"object\",\n \"properties\": {\n \"simpleValue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"originalData\": {\n \"type\": \"object\",\n \"title\": \"Original Data\",\n \"description\": \"The original data structure that was analyzed\",\n \"properties\": {\n \"simpleValue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n }\n }\n },\n \"counts\": {\n \"type\": \"object\",\n \"title\": \"Value Counts\",\n \"description\": \"Count of occurrences for each unique value\",\n \"additionalProperties\": {\n \"type\": \"integer\",\n \"minimum\": 1\n }\n },\n \"totalCount\": {\n \"type\": \"integer\",\n \"title\": \"Total Count\",\n \"description\": \"Total number of values in the array\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"uniqueCount\": {\n \"type\": \"integer\",\n \"title\": \"Unique Count\",\n \"description\": \"Number of unique values in the array\",\n \"minimum\": 0,\n \"default\": 0\n }\n },\n \"title\": \"Value Counter\",\n \"description\": \"A utility to count occurrences of values in an array.\"\n}\n\n\n\n# SDK Usage Guide\n\n## 1. `useDoc` Hook\n\nThe `useDoc` hook binds to a reactive cell given key and returns a tuple `[doc, setDoc]`:\n\nAny keys from the view-model-schema are valid for useDoc, any other keys will fail. Provide a default as the second argument, **do not set an initial value explicitly**.\n\nFor this schema:\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"counter\": {\n \"type\": \"number\",\n },\n \"title\": {\n \"type\": \"string\",\n \"default\": \"My Counter App\"\n }\n }\n}\n```\n\n```jsx\nfunction CounterComponent() {\n // Correct: useDoc called at top level of component\n const [counter, setCounter] = useDoc(\"counter\", -1); // default\n\n // Incorrect: would cause errors\n // if(something) {\n // const [data, setData] = useDoc(\"data\", {}); // Never do this!\n // }\n\n const onIncrement = useCallback(() => {\n // writing to the cell automatically triggers a re-render\n setCounter(counter + 1);\n }, [counter]);\n\n return (\n \n );\n}\n```\n\n## 2. llm Function\n\n```jsx\nasync function fetchLLMResponse() {\n // place user-level requirements in system prompt\n const promptPayload = {\n system: 'Translate all the messages to emojis, reply in JSON.',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke']\n };\n // grabJson is available on the window, string -> JSON\n const result = grabJson(await llm(promptPayload));\n console.log('LLM responded:', result);\n}\n```\n\n## 3. readWebpage Function\n\n```jsx\nasync function fetchFromUrl() {\n const url = 'https://twopm.studio';\n const result = await readWebpage(url);\n console.log('Markdown:', result.content);\n}\n```\n\n## 4. generateImage Function\n\n```jsx\nfunction ImageComponent() {\n return \"Generated;\n}\n\n```\n## 5. Using the Interface Functions\n\n```javascript\n// Import from modern ESM libraries:\n// - react\n// - react-dom\n// - react-dom/client\n// - d3\n// - moment\n// - marked\n// - @react-spring/web\n// - @use-gesture/react\n// - uuid\n// - tone\nfunction onLoad() {\n return ['@react-spring/web']; // Request the modules you need\n}\n\nconst title = 'My ESM App';\nfunction ImageComponent({ url }) {\n return \"Generated;\n}\n\nfunction MyComponent({ label, description }) {\n return (\n
\n

{label}

\n

{description}

\n \n
\n );\n}\n\nfunction TodoItem({ todo, onToggle, onDelete }) {\n return (\n
\n \n \n {todo.text}\n \n \n Delete\n \n
\n );\n}\n\nfunction TodoList({ todo, setTodos}) {\n const [newTodo, setNewTodo] = React.useState('');\n\n const addTodo = () => {\n if (newTodo.trim() === '') return;\n\n const newTodoItem = {\n id: Date.now(),\n text: newTodo,\n completed: false\n };\n\n setTodos([...todos, newTodoItem]);\n setNewTodo('');\n };\n\n const toggleTodo = (id) => {\n setTodos(todos.map(todo =>\n todo.id === id ? { ...todo, completed: !todo.completed } : todo\n ));\n };\n\n const deleteTodo = (id) => {\n setTodos(todos.filter(todo => todo.id !== id));\n };\n\n return (\n
\n

Todo List

\n\n
\n setNewTodo(e.target.value)}\n placeholder=\"Add a new todo\"\n className=\"flex-grow p-2 border rounded-l\"\n />\n \n Add\n \n
\n\n
\n {todos.length > 0 ? (\n todos.map(todo => (\n toggleTodo(todo.id)}\n onDelete={() => deleteTodo(todo.id)}\n />\n ))\n ) : (\n

No todos yet!

\n )}\n
\n
\n );\n}\n\n// Main application code with modules passed as third parameter\nfunction onReady(mount, sourceData, libs) {\n const { useState, useEffect } = React; // React is available globally\n const { useSpring, animated } = libs['@react-spring/web']; // Access imported module\n\n function MyApp() {\n const [count, setCount] = useDoc('count', 0);\n const [todos, setTodos] = useDoc('todos', [\n { id: 1, text: 'Learn React', completed: false },\n { id: 2, text: 'Build a Todo App', completed: false }\n ]);\n const props = useSpring({\n from: { opacity: 0 },\n to: { opacity: 1 }\n });\n\n return (\n
\n \n setCount(count + 1)}\n >\n Clicks: {count}\n \n \n \n
\n );\n }\n\n // Use the client API for React 18\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```\n\n", - "messages": [ - { - "role": "user", - "content": "The user asked you to create the source code with the following specification:\n```\ncount of values\n1. Parse the existing object and extract the \"values\" array from simpleValue2.\n2. Count occurrences of each unique string value in the array.\n3. Generate a result object containing the original data plus the count information.\nThe Value Counter will analyze the \"values\" array within the simpleValue2 object and count the occurrences of each unique string. It will generate a report showing each unique value and its count. The counter will handle empty arrays and maintain the original data structure while adding the count results.\n```" - }, - { - "role": "assistant", - "content": "\nfunction onLoad() {\n return []; // No additional libraries needed\n}\n\nconst title = 'Value Counter';\n\nfunction onReady(mount, sourceData) {\n function ValueCounter() {\n // Get the original data structure\n const [simpleValue2, setSimpleValue2] = useDoc(\"simpleValue2\", { values: [] });\n const [originalData, setOriginalData] = useDoc(\"originalData\", { simpleValue2: { values: [] } });\n const [counts, setCounts] = useDoc(\"counts\", {});\n const [totalCount, setTotalCount] = useDoc(\"totalCount\", 0);\n const [uniqueCount, setUniqueCount] = useDoc(\"uniqueCount\", 0);\n \n // Local state for new value input\n const [newValue, setNewValue] = React.useState(\"\");\n \n // Process the values array and count occurrences\n React.useEffect(() => {\n const values = simpleValue2.values || [];\n \n // Update original data\n setOriginalData({ simpleValue2: { values: [...values] } });\n \n // Count occurrences of each unique value\n const countMap = {};\n values.forEach(value => {\n countMap[value] = (countMap[value] || 0) + 1;\n });\n \n // Update the counts\n setCounts(countMap);\n setTotalCount(values.length);\n setUniqueCount(Object.keys(countMap).length);\n }, [simpleValue2.values]);\n \n // Add a new value to the array\n const handleAddValue = () => {\n if (newValue.trim() === \"\") return;\n \n const updatedValues = [...(simpleValue2.values || []), newValue.trim()];\n setSimpleValue2({ values: updatedValues });\n setNewValue(\"\");\n };\n \n // Remove a value from the array\n const handleRemoveValue = (valueToRemove) => {\n const updatedValues = (simpleValue2.values || []).filter(\n (_, index) => index !== valueToRemove\n );\n setSimpleValue2({ values: updatedValues });\n };\n \n // Clear all values\n const handleClearAll = () => {\n setSimpleValue2({ values: [] });\n };\n\n return (\n
\n

{title}

\n \n {/* Input section */}\n
\n

Add Values

\n
\n setNewValue(e.target.value)}\n className=\"flex-grow p-2 border rounded-l focus:outline-none focus:ring-2 focus:ring-blue-500\"\n placeholder=\"Enter a value\"\n />\n \n Add\n \n
\n \n {/* Current values list */}\n
\n
\n

Current Values:

\n {simpleValue2.values && simpleValue2.values.length > 0 && (\n \n Clear All\n \n )}\n
\n \n {simpleValue2.values && simpleValue2.values.length > 0 ? (\n
    \n {simpleValue2.values.map((value, index) => (\n
  • \n {value}\n handleRemoveValue(index)}\n className=\"text-red-500 hover:text-red-700\"\n >\n ❌\n \n
  • \n ))}\n
\n ) : (\n

No values added yet

\n )}\n
\n
\n \n {/* Results section */}\n
\n {/* Count summary */}\n
\n

Count Summary

\n
\n
\n

Total Values

\n

{totalCount}

\n
\n
\n

Unique Values

\n

{uniqueCount}

\n
\n
\n
\n \n {/* Value counts */}\n
\n

Value Counts

\n {Object.keys(counts).length > 0 ? (\n
\n \n \n \n \n \n \n \n \n \n {Object.entries(counts).map(([value, count]) => (\n \n \n \n \n \n ))}\n \n
ValueCountPercentage
{value}{count}\n {totalCount > 0 ? ((count / totalCount) * 100).toFixed(1) + \"%\" : \"0%\"}\n
\n
\n ) : (\n

No data to display

\n )}\n
\n
\n
\n );\n }\n\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```" - }, - { - "role": "assistant", - "content": "\nfunction onLoad() {\n return []; // No additional libraries needed\n}\n\nconst title = 'Value Counter';\n\nfunction onReady(mount, sourceData) {\n function ValueCounter() {\n // Get the original data structure\n const [simpleValue2, setSimpleValue2] = useDoc(\"simpleValue2\", { values: [] });\n const [originalData, setOriginalData] = useDoc(\"originalData\", { simpleValue2: { values: [] } });\n const [counts, setCounts] = useDoc(\"counts\", {});\n const [totalCount, setTotalCount] = useDoc(\"totalCount\", 0);\n const [uniqueCount, setUniqueCount] = useDoc(\"uniqueCount\", 0);\n \n // Local state for new value input\n const [newValue, setNewValue] = React.useState(\"\");\n \n // Process the values array and count occurrences\n React.useEffect(() => {\n const values = simpleValue2.values || [];\n \n // Update original data\n setOriginalData({ simpleValue2: { values: [...values] } });\n \n // Count occurrences of each unique value\n const countMap = {};\n values.forEach(value => {\n countMap[value] = (countMap[value] || 0) + 1;\n });\n \n // Update the counts\n setCounts(countMap);\n setTotalCount(values.length);\n setUniqueCount(Object.keys(countMap).length);\n }, [simpleValue2.values]);\n \n // Add a new value to the array\n const handleAddValue = () => {\n if (newValue.trim() === \"\") return;\n \n const updatedValues = [...(simpleValue2.values || []), newValue.trim()];\n setSimpleValue2({ values: updatedValues });\n setNewValue(\"\");\n };\n \n // Remove a value from the array\n const handleRemoveValue = (valueToRemove) => {\n const updatedValues = (simpleValue2.values || []).filter(\n (_, index) => index !== valueToRemove\n );\n setSimpleValue2({ values: updatedValues });\n };\n \n // Clear all values\n const handleClearAll = () => {\n setSimpleValue2({ values: [] });\n };\n\n return (\n
\n

{title}

\n \n {/* Input section */}\n
\n

Add Values

\n
\n setNewValue(e.target.value)}\n className=\"flex-grow p-2 border rounded-l focus:outline-none focus:ring-2 focus:ring-blue-500\"\n placeholder=\"Enter a value\"\n />\n \n Add\n \n
\n \n {/* Current values list */}\n
\n
\n

Current Values:

\n {simpleValue2.values && simpleValue2.values.length > 0 && (\n \n Clear All\n \n )}\n
\n \n {simpleValue2.values && simpleValue2.values.length > 0 ? (\n
    \n {simpleValue2.values.map((value, index) => (\n
  • \n {value}\n handleRemoveValue(index)}\n className=\"text-red-500 hover:text-red-700\"\n >\n ❌\n \n
  • \n ))}\n
\n ) : (\n

No values added yet

\n )}\n
\n
\n \n {/* Results section */}\n
\n {/* Count summary */}\n
\n

Count Summary

\n
\n
\n

Total Values

\n

{totalCount}

\n
\n
\n

Unique Values

\n

{uniqueCount}

\n
\n
\n
\n \n {/* Value counts */}\n
\n

Value Counts

\n {Object.keys(counts).length > 0 ? (\n
\n \n \n \n \n \n \n \n \n \n {Object.entries(counts).map(([value, count]) => (\n \n \n \n \n \n ))}\n \n
ValueCountPercentage
{value}{count}\n {totalCount > 0 ? ((count / totalCount) * 100).toFixed(1) + \"%\" : \"0%\"}\n
\n
\n ) : (\n

No data to display

\n )}\n
\n
\n
\n );\n }\n\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```" - } - ], - "stop": "\n```", - "stream": false -} \ No newline at end of file diff --git a/jumble/integration/cache/llm-api-cache/c02a73eaaab29ba5c9f480e301afa7f4dd0f0009012cf01e01c725a837301f03.json b/jumble/integration/cache/llm-api-cache/c02a73eaaab29ba5c9f480e301afa7f4dd0f0009012cf01e01c725a837301f03.json index c32ce2ec5..0c5ad8c24 100644 --- a/jumble/integration/cache/llm-api-cache/c02a73eaaab29ba5c9f480e301afa7f4dd0f0009012cf01e01c725a837301f03.json +++ b/jumble/integration/cache/llm-api-cache/c02a73eaaab29ba5c9f480e301afa7f4dd0f0009012cf01e01c725a837301f03.json @@ -9,11 +9,11 @@ }, { "role": "assistant", - "content": "\nValue Counter\n\n\n\nA utility to count occurrences of values in an array.\n\n\n\nThe Value Counter will analyze the \"values\" array within the simpleValue2 object and count the occurrences of each unique string. It will generate a report showing each unique value and its count. The counter will handle empty arrays and maintain the original data structure while adding the count results.\n\n\n\n1. Parse the existing object and extract the \"values\" array from simpleValue2.\n2. Count occurrences of each unique string value in the array.\n3. Generate a result object containing the original data plus the count information.\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"Value Count Result\",\n \"description\": \"Result of counting values in an array\",\n \"properties\": {\n \"originalData\": {\n \"type\": \"object\",\n \"title\": \"Original Data\",\n \"description\": \"The original data structure that was analyzed\",\n \"properties\": {\n \"simpleValue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n }\n }\n },\n \"counts\": {\n \"type\": \"object\",\n \"title\": \"Value Counts\",\n \"description\": \"Count of occurrences for each unique value\",\n \"additionalProperties\": {\n \"type\": \"integer\",\n \"minimum\": 1\n }\n },\n \"totalCount\": {\n \"type\": \"integer\",\n \"title\": \"Total Count\",\n \"description\": \"Total number of values in the array\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"uniqueCount\": {\n \"type\": \"integer\",\n \"title\": \"Unique Count\",\n \"description\": \"Number of unique values in the array\",\n \"minimum\": 0,\n \"default\": 0\n }\n },\n \"required\": [\"originalData\", \"counts\", \"totalCount\", \"uniqueCount\"]\n}\n" + "content": "\nValue Counter\n\n\n\nA utility to count occurrences of values in an array.\n\n\n\nThe Value Counter will analyze the \"values\" array within the simpleValue2 object and count the occurrences of each unique string. It will return a summary object containing each unique value as a key and its count as the corresponding value. This simple counter will maintain the existing schema structure while providing useful aggregation functionality.\n\n\n\n1. Extract the \"values\" array from the simpleValue2 object in the existing schema\n2. Process the array to count occurrences of each unique string value\n3. Return a result object with the counts mapped by value\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"Value Count Result\",\n \"description\": \"Count of occurrences for each unique value in the array\",\n \"properties\": {\n \"counts\": {\n \"type\": \"object\",\n \"title\": \"Value Counts\",\n \"description\": \"Map of values to their occurrence counts\",\n \"additionalProperties\": {\n \"type\": \"integer\",\n \"minimum\": 1\n }\n },\n \"total\": {\n \"type\": \"integer\",\n \"title\": \"Total Count\",\n \"description\": \"Total number of values counted\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"uniqueCount\": {\n \"type\": \"integer\",\n \"title\": \"Unique Count\",\n \"description\": \"Number of unique values found\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"source\": {\n \"type\": \"string\",\n \"title\": \"Source\",\n \"description\": \"Identifier of the source array that was counted\",\n \"default\": \"simpleValue2.values\"\n }\n },\n \"required\": [\"counts\", \"total\", \"uniqueCount\"]\n}\n" }, { "role": "assistant", - "content": "\nValue Counter\n\n\n\nA utility to count occurrences of values in an array.\n\n\n\nThe Value Counter will analyze the \"values\" array within the simpleValue2 object and count the occurrences of each unique string. It will generate a report showing each unique value and its count. The counter will handle empty arrays and maintain the original data structure while adding the count results.\n\n\n\n1. Parse the existing object and extract the \"values\" array from simpleValue2.\n2. Count occurrences of each unique string value in the array.\n3. Generate a result object containing the original data plus the count information.\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"Value Count Result\",\n \"description\": \"Result of counting values in an array\",\n \"properties\": {\n \"originalData\": {\n \"type\": \"object\",\n \"title\": \"Original Data\",\n \"description\": \"The original data structure that was analyzed\",\n \"properties\": {\n \"simpleValue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n }\n }\n },\n \"counts\": {\n \"type\": \"object\",\n \"title\": \"Value Counts\",\n \"description\": \"Count of occurrences for each unique value\",\n \"additionalProperties\": {\n \"type\": \"integer\",\n \"minimum\": 1\n }\n },\n \"totalCount\": {\n \"type\": \"integer\",\n \"title\": \"Total Count\",\n \"description\": \"Total number of values in the array\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"uniqueCount\": {\n \"type\": \"integer\",\n \"title\": \"Unique Count\",\n \"description\": \"Number of unique values in the array\",\n \"minimum\": 0,\n \"default\": 0\n }\n },\n \"required\": [\"originalData\", \"counts\", \"totalCount\", \"uniqueCount\"]\n}\n" + "content": "\nValue Counter\n\n\n\nA utility to count occurrences of values in an array.\n\n\n\nThe Value Counter will analyze the \"values\" array within the simpleValue2 object and count the occurrences of each unique string. It will return a summary object containing each unique value as a key and its count as the corresponding value. This simple counter will maintain the existing schema structure while providing useful aggregation functionality.\n\n\n\n1. Extract the \"values\" array from the simpleValue2 object in the existing schema\n2. Process the array to count occurrences of each unique string value\n3. Return a result object with the counts mapped by value\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"Value Count Result\",\n \"description\": \"Count of occurrences for each unique value in the array\",\n \"properties\": {\n \"counts\": {\n \"type\": \"object\",\n \"title\": \"Value Counts\",\n \"description\": \"Map of values to their occurrence counts\",\n \"additionalProperties\": {\n \"type\": \"integer\",\n \"minimum\": 1\n }\n },\n \"total\": {\n \"type\": \"integer\",\n \"title\": \"Total Count\",\n \"description\": \"Total number of values counted\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"uniqueCount\": {\n \"type\": \"integer\",\n \"title\": \"Unique Count\",\n \"description\": \"Number of unique values found\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"source\": {\n \"type\": \"string\",\n \"title\": \"Source\",\n \"description\": \"Identifier of the source array that was counted\",\n \"default\": \"simpleValue2.values\"\n }\n },\n \"required\": [\"counts\", \"total\", \"uniqueCount\"]\n}\n" } ] } \ No newline at end of file diff --git a/jumble/integration/cache/llm-api-cache/cfbaaaf5efb72c9d307842196b35ed8a9106ec2f5e150b3f95b4fcab9d1f112f.json b/jumble/integration/cache/llm-api-cache/cfbaaaf5efb72c9d307842196b35ed8a9106ec2f5e150b3f95b4fcab9d1f112f.json index 5090ee82f..dfd73f789 100644 --- a/jumble/integration/cache/llm-api-cache/cfbaaaf5efb72c9d307842196b35ed8a9106ec2f5e150b3f95b4fcab9d1f112f.json +++ b/jumble/integration/cache/llm-api-cache/cfbaaaf5efb72c9d307842196b35ed8a9106ec2f5e150b3f95b4fcab9d1f112f.json @@ -9,11 +9,11 @@ }, { "role": "assistant", - "content": "\nValue Counter\n\n\n\nA tool to count and track the frequency of values.\n\n\n\nThe Value Counter allows users to track occurrences of different values or items. It maintains a running count for each unique value entered and displays the current totals. The counter should support adding new values, incrementing existing ones, and viewing the complete count distribution.\n\n\n\n1. Create a data structure to store unique values and their counts\n2. Implement functions to add/increment values and retrieve counts\n3. Build a simple interface to display and update the counts\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"ValueCounter\",\n \"description\": \"Tracks the frequency of different values\",\n \"properties\": {\n \"counts\": {\n \"type\": \"object\",\n \"title\": \"Counts\",\n \"description\": \"Map of values to their occurrence counts\",\n \"additionalProperties\": {\n \"type\": \"integer\",\n \"minimum\": 0\n },\n \"default\": {}\n },\n \"totalItems\": {\n \"type\": \"integer\",\n \"title\": \"Total Items\",\n \"description\": \"Total number of items counted\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"title\": \"Last Updated\",\n \"description\": \"When the counter was last updated\"\n },\n \"sortBy\": {\n \"type\": \"string\",\n \"title\": \"Sort By\",\n \"description\": \"How to sort the values when displaying\",\n \"enum\": [\"value\", \"count\", \"none\"],\n \"default\": \"none\"\n }\n },\n \"required\": [\"counts\", \"totalItems\"]\n}\n\n\n\n{\n \"counts\": {\n \"apple\": 5,\n \"banana\": 3,\n \"orange\": 2,\n \"grape\": 1\n },\n \"totalItems\": 11,\n \"lastUpdated\": \"2023-05-15T14:30:45Z\",\n \"sortBy\": \"count\"\n}\n" + "content": "\nValue Counter\n\n\n\nA tool to count and track the frequency of values.\n\n\n\nThe Value Counter allows users to track occurrences of different values. It maintains a running count for each unique value entered. Users can add new values, increment existing ones, and view the current counts for all tracked values.\n\n\n\n1. Create a data structure to store values and their counts\n2. Implement functions to add/increment values and retrieve counts\n3. Build a simple interface to interact with the counter\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"ValueCounter\",\n \"description\": \"Tracks the frequency of different values\",\n \"properties\": {\n \"counts\": {\n \"type\": \"object\",\n \"title\": \"Counts\",\n \"description\": \"Map of values to their occurrence counts\",\n \"additionalProperties\": {\n \"type\": \"integer\",\n \"minimum\": 0\n },\n \"default\": {}\n },\n \"totalCount\": {\n \"type\": \"integer\",\n \"title\": \"Total Count\",\n \"description\": \"Sum of all value counts\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"title\": \"Last Updated\",\n \"description\": \"When the counter was last modified\"\n }\n },\n \"required\": [\"counts\", \"totalCount\"]\n}\n\n\n\n{\n \"counts\": {\n \"apple\": 5,\n \"banana\": 3,\n \"orange\": 2,\n \"grape\": 1\n },\n \"totalCount\": 11,\n \"lastUpdated\": \"2023-05-15T14:30:45Z\"\n}\n" }, { "role": "assistant", - "content": "\nValue Counter\n\n\n\nA tool to count and track the frequency of values.\n\n\n\nThe Value Counter allows users to track occurrences of different values or items. It maintains a running count for each unique value entered and displays the current totals. The counter should support adding new values, incrementing existing ones, and viewing the complete count distribution.\n\n\n\n1. Create a data structure to store unique values and their counts\n2. Implement functions to add/increment values and retrieve counts\n3. Build a simple interface to display and update the counts\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"ValueCounter\",\n \"description\": \"Tracks the frequency of different values\",\n \"properties\": {\n \"counts\": {\n \"type\": \"object\",\n \"title\": \"Counts\",\n \"description\": \"Map of values to their occurrence counts\",\n \"additionalProperties\": {\n \"type\": \"integer\",\n \"minimum\": 0\n },\n \"default\": {}\n },\n \"totalItems\": {\n \"type\": \"integer\",\n \"title\": \"Total Items\",\n \"description\": \"Total number of items counted\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"title\": \"Last Updated\",\n \"description\": \"When the counter was last updated\"\n },\n \"sortBy\": {\n \"type\": \"string\",\n \"title\": \"Sort By\",\n \"description\": \"How to sort the values when displaying\",\n \"enum\": [\"value\", \"count\", \"none\"],\n \"default\": \"none\"\n }\n },\n \"required\": [\"counts\", \"totalItems\"]\n}\n\n\n\n{\n \"counts\": {\n \"apple\": 5,\n \"banana\": 3,\n \"orange\": 2,\n \"grape\": 1\n },\n \"totalItems\": 11,\n \"lastUpdated\": \"2023-05-15T14:30:45Z\",\n \"sortBy\": \"count\"\n}\n" + "content": "\nValue Counter\n\n\n\nA tool to count and track the frequency of values.\n\n\n\nThe Value Counter allows users to track occurrences of different values. It maintains a running count for each unique value entered. Users can add new values, increment existing ones, and view the current counts for all tracked values.\n\n\n\n1. Create a data structure to store values and their counts\n2. Implement functions to add/increment values and retrieve counts\n3. Build a simple interface to interact with the counter\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"ValueCounter\",\n \"description\": \"Tracks the frequency of different values\",\n \"properties\": {\n \"counts\": {\n \"type\": \"object\",\n \"title\": \"Counts\",\n \"description\": \"Map of values to their occurrence counts\",\n \"additionalProperties\": {\n \"type\": \"integer\",\n \"minimum\": 0\n },\n \"default\": {}\n },\n \"totalCount\": {\n \"type\": \"integer\",\n \"title\": \"Total Count\",\n \"description\": \"Sum of all value counts\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"title\": \"Last Updated\",\n \"description\": \"When the counter was last modified\"\n }\n },\n \"required\": [\"counts\", \"totalCount\"]\n}\n\n\n\n{\n \"counts\": {\n \"apple\": 5,\n \"banana\": 3,\n \"orange\": 2,\n \"grape\": 1\n },\n \"totalCount\": 11,\n \"lastUpdated\": \"2023-05-15T14:30:45Z\"\n}\n" } ] } \ No newline at end of file diff --git a/jumble/integration/cache/llm-api-cache/d62fdd4fd8616f9e94add2234ce7bb0536bfbe59a9a68150658ce044a856ba66.json b/jumble/integration/cache/llm-api-cache/d62fdd4fd8616f9e94add2234ce7bb0536bfbe59a9a68150658ce044a856ba66.json new file mode 100644 index 000000000..02d28eff6 --- /dev/null +++ b/jumble/integration/cache/llm-api-cache/d62fdd4fd8616f9e94add2234ce7bb0536bfbe59a9a68150658ce044a856ba66.json @@ -0,0 +1,20 @@ +{ + "model": "anthropic:claude-3-7-sonnet-latest", + "system": "# React Component Builder\n\nCreate an interactive React component that fulfills the user's request. Focus on delivering a clean, useful implementation with appropriate features.\n\n## You Are Part of a Two-Phase Process\n\n1. First phase (already completed):\n - Analyzed the user's request\n - Created a detailed specification\n - Generated a structured data schema\n\n2. Your job (second phase):\n - Create a reactive UI component based on the provided specification and schema\n - Implement the UI exactly according to the specification\n - Strictly adhere to the data schema provided\n\n## Required Elements\n- Define a title with `const title = 'Your App Name';`\n- Implement both `onLoad` and `onReady` functions\n- Use Tailwind CSS for styling with tasteful defaults\n- Do not write inline, use emoji for icons\n- Carefully avoid infinite loops and recursion that may cause performance issues\n\n## Code Structure\n1. React and ReactDOM are pre-imported - don't import them again\n2. All React hooks must be namespaced (e.g., `React.useState`, `React.useEffect`)\n3. Follow React hooks rules - never nest or conditionally call hooks\n4. For form handling, use `onClick` handlers instead of `onSubmit`\n\n## Available APIs\n- **useDoc(key, defaultValue)** - Persistent data storage with reactive updates\n- **llm(promptPayload)** - Send requests to the language model\n- **readWebpage(url)** - Fetch and parse external web content\n- **generateImage(prompt)** - Create AI-generated images\n\n## Important Note About useDoc\n- **useDoc is a React Hook** and must follow all React hook rules\n- It should only be used for persistent state and must draw from the provided schema\n - For any ephemeral state, use `React.useState`\n- Only call useDoc at the top level of your function components or custom hooks\n- Do not call useDoc inside loops, conditions, or nested functions\n- useDoc cannot be used outside of `onReady` components - it must be called during rendering\n\n## Library Usage\n- Request additional libraries in `onLoad` by returning an array of module names\n- Available libraries:\n - imports : [object Object]\n- Only use the explicitly provided libraries\n\n## Security Restrictions\n- Do not use browser dialog functions (`prompt()`, `alert()`, `confirm()`)\n- Avoid any methods that could compromise security or user experience\n\n\n\n{\n \"type\": \"object\",\n \"properties\": {\n \"simpleValue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"counts\": {\n \"type\": \"object\",\n \"title\": \"Value Counts\",\n \"description\": \"Map of each unique value to its count\",\n \"additionalProperties\": {\n \"type\": \"integer\",\n \"minimum\": 1\n }\n },\n \"total\": {\n \"type\": \"integer\",\n \"title\": \"Total Count\",\n \"description\": \"Total number of values counted\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"uniqueCount\": {\n \"type\": \"integer\",\n \"title\": \"Unique Count\",\n \"description\": \"Number of unique values found\",\n \"minimum\": 0,\n \"default\": 0\n },\n \"source\": {\n \"type\": \"string\",\n \"title\": \"Source\",\n \"description\": \"Identifier of the source array that was counted\",\n \"default\": \"simpleValue2.values\"\n }\n },\n \"title\": \"Value Counter\",\n \"description\": \"A utility to count occurrences of values in an array.\"\n}\n\n\n\n# SDK Usage Guide\n\n## 1. `useDoc` Hook\n\nThe `useDoc` hook binds to a reactive cell given key and returns a tuple `[doc, setDoc]`:\n\nAny keys from the view-model-schema are valid for useDoc, any other keys will fail. Provide a default as the second argument, **do not set an initial value explicitly**.\n\nFor this schema:\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"counter\": {\n \"type\": \"number\",\n },\n \"title\": {\n \"type\": \"string\",\n \"default\": \"My Counter App\"\n }\n }\n}\n```\n\n```jsx\nfunction CounterComponent() {\n // Correct: useDoc called at top level of component\n const [counter, setCounter] = useDoc(\"counter\", -1); // default\n\n // Incorrect: would cause errors\n // if(something) {\n // const [data, setData] = useDoc(\"data\", {}); // Never do this!\n // }\n\n const onIncrement = useCallback(() => {\n // writing to the cell automatically triggers a re-render\n setCounter(counter + 1);\n }, [counter]);\n\n return (\n \n );\n}\n```\n\n## 2. llm Function\n\n```jsx\nasync function fetchLLMResponse() {\n // place user-level requirements in system prompt\n const promptPayload = {\n system: 'Translate all the messages to emojis, reply in JSON.',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke']\n };\n const result = await llm(promptPayload)\n console.log('LLM responded:', result);\n}\n```\n\nIf you need JSON to be returned from the LLM, you can enable the `mode: 'json'` in the `promptPayload`.\n\n```jsx\nconst promptPayload = {\n system: 'Translate all the messages to emojis, reply in JSON.',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke'],\n mode: 'json'\n};\nconst result = await llm(promptPayload);\nconsole.log('JSON response from llm:', result);\n```\n\n## 3. readWebpage Function\n\n```jsx\nasync function fetchFromUrl() {\n const url = 'https://twopm.studio';\n const result = await readWebpage(url);\n console.log('Markdown:', result.content);\n}\n```\n\n## 4. generateImage Function\n\n```jsx\nfunction ImageComponent() {\n return \"Generated;\n}\n\n```\n## 5. Using the Interface Functions\n\n```javascript\n// Import from modern ESM libraries:\n// - react\n// - react-dom\n// - react-dom/client\n// - d3\n// - moment\n// - marked\n// - @react-spring/web\n// - @use-gesture/react\n// - uuid\n// - tone\nfunction onLoad() {\n return ['@react-spring/web']; // Request the modules you need\n}\n\nconst title = 'My ESM App';\nfunction ImageComponent({ url }) {\n return \"Generated;\n}\n\nfunction MyComponent({ label, description }) {\n return (\n
\n

{label}

\n

{description}

\n \n
\n );\n}\n\nfunction TodoItem({ todo, onToggle, onDelete }) {\n return (\n
\n \n \n {todo.text}\n \n \n Delete\n \n
\n );\n}\n\nfunction TodoList({ todo, setTodos}) {\n const [newTodo, setNewTodo] = React.useState('');\n\n const addTodo = () => {\n if (newTodo.trim() === '') return;\n\n const newTodoItem = {\n id: Date.now(),\n text: newTodo,\n completed: false\n };\n\n setTodos([...todos, newTodoItem]);\n setNewTodo('');\n };\n\n const toggleTodo = (id) => {\n setTodos(todos.map(todo =>\n todo.id === id ? { ...todo, completed: !todo.completed } : todo\n ));\n };\n\n const deleteTodo = (id) => {\n setTodos(todos.filter(todo => todo.id !== id));\n };\n\n return (\n
\n

Todo List

\n\n
\n setNewTodo(e.target.value)}\n placeholder=\"Add a new todo\"\n className=\"flex-grow p-2 border rounded-l\"\n />\n \n Add\n \n
\n\n
\n {todos.length > 0 ? (\n todos.map(todo => (\n toggleTodo(todo.id)}\n onDelete={() => deleteTodo(todo.id)}\n />\n ))\n ) : (\n

No todos yet!

\n )}\n
\n
\n );\n}\n\n// Main application code with modules passed as third parameter\nfunction onReady(mount, sourceData, libs) {\n const { useState, useEffect } = React; // React is available globally\n const { useSpring, animated } = libs['@react-spring/web']; // Access imported module\n\n function MyApp() {\n const [count, setCount] = useDoc('count', 0);\n const [todos, setTodos] = useDoc('todos', [\n { id: 1, text: 'Learn React', completed: false },\n { id: 2, text: 'Build a Todo App', completed: false }\n ]);\n const props = useSpring({\n from: { opacity: 0 },\n to: { opacity: 1 }\n });\n\n return (\n
\n \n setCount(count + 1)}\n >\n Clicks: {count}\n \n \n \n
\n );\n }\n\n // Use the client API for React 18\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```\n\n", + "messages": [ + { + "role": "user", + "content": "The user asked you to create the source code with the following specification:\n```\ncount of values\n1. Extract the \"values\" array from the simpleValue2 object in the existing schema\n2. Process the array to count occurrences of each unique string value\n3. Return a result object with the counts mapped by value\nThe Value Counter will analyze the \"values\" array within the simpleValue2 object and count the occurrences of each unique string. It will return a summary object containing each unique value as a key and its count as the corresponding value. This simple counter will maintain the existing schema structure while providing useful aggregation functionality.\n```" + }, + { + "role": "assistant", + "content": "\nfunction onLoad() {\n return []; // No additional libraries needed\n}\n\nconst title = 'Value Counter';\n\nfunction onReady(mount) {\n function ValueCounter() {\n // Get the source array from simpleValue2.values\n const [simpleValue2, setSimpleValue2] = useDoc(\"simpleValue2\", { values: [] });\n const [counts, setCounts] = useDoc(\"counts\", {});\n const [total, setTotal] = useDoc(\"total\", 0);\n const [uniqueCount, setUniqueCount] = useDoc(\"uniqueCount\", 0);\n const [source, setSource] = useDoc(\"source\", \"simpleValue2.values\");\n \n // Local state for input field\n const [newValue, setNewValue] = React.useState(\"\");\n \n // Process the array to count occurrences\n React.useEffect(() => {\n if (!simpleValue2 || !simpleValue2.values) return;\n \n const valueArray = simpleValue2.values;\n const countMap = {};\n \n // Count occurrences of each value\n valueArray.forEach(value => {\n countMap[value] = (countMap[value] || 0) + 1;\n });\n \n // Update the counts, total, and uniqueCount\n setCounts(countMap);\n setTotal(valueArray.length);\n setUniqueCount(Object.keys(countMap).length);\n }, [simpleValue2, setCounts, setTotal, setUniqueCount]);\n \n // Handle adding a new value\n const handleAddValue = () => {\n if (!newValue.trim()) return;\n \n const updatedValues = [...(simpleValue2.values || []), newValue.trim()];\n setSimpleValue2({ ...simpleValue2, values: updatedValues });\n setNewValue(\"\");\n };\n \n // Handle removing a value\n const handleRemoveValue = (valueToRemove) => {\n const updatedValues = simpleValue2.values.filter(value => value !== valueToRemove);\n setSimpleValue2({ ...simpleValue2, values: updatedValues });\n };\n \n // Handle clearing all values\n const handleClearAll = () => {\n setSimpleValue2({ values: [] });\n };\n \n return (\n
\n

{title}

\n \n {/* Input section */}\n
\n

Add Values

\n
\n setNewValue(e.target.value)}\n className=\"flex-grow p-2 border rounded-l focus:outline-none focus:ring-2 focus:ring-blue-500\"\n placeholder=\"Enter a value\"\n />\n \n Add\n \n
\n \n
\n \n Clear All\n \n
\n
\n \n {/* Values list */}\n
\n
\n

Values List

\n {simpleValue2.values && simpleValue2.values.length > 0 ? (\n
    \n {simpleValue2.values.map((value, index) => (\n
  • \n {value}\n handleRemoveValue(value)}\n className=\"text-red-500 hover:text-red-700\"\n >\n ❌\n \n
  • \n ))}\n
\n ) : (\n

No values added yet

\n )}\n
\n \n {/* Counts display */}\n
\n

Value Counts

\n {Object.keys(counts).length > 0 ? (\n
    \n {Object.entries(counts).map(([value, count], index) => (\n
  • \n {value}\n \n {count}\n \n
  • \n ))}\n
\n ) : (\n

No counts available

\n )}\n
\n
\n \n {/* Summary statistics */}\n
\n

Summary

\n
\n
\n

Total Values

\n

{total}

\n
\n
\n

Unique Values

\n

{uniqueCount}

\n
\n
\n

Source

\n

{source}

\n
\n
\n
\n
\n );\n }\n\n // Render the component\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```" + }, + { + "role": "assistant", + "content": "\nfunction onLoad() {\n return []; // No additional libraries needed\n}\n\nconst title = 'Value Counter';\n\nfunction onReady(mount) {\n function ValueCounter() {\n // Get the source array from simpleValue2.values\n const [simpleValue2, setSimpleValue2] = useDoc(\"simpleValue2\", { values: [] });\n const [counts, setCounts] = useDoc(\"counts\", {});\n const [total, setTotal] = useDoc(\"total\", 0);\n const [uniqueCount, setUniqueCount] = useDoc(\"uniqueCount\", 0);\n const [source, setSource] = useDoc(\"source\", \"simpleValue2.values\");\n \n // Local state for input field\n const [newValue, setNewValue] = React.useState(\"\");\n \n // Process the array to count occurrences\n React.useEffect(() => {\n if (!simpleValue2 || !simpleValue2.values) return;\n \n const valueArray = simpleValue2.values;\n const countMap = {};\n \n // Count occurrences of each value\n valueArray.forEach(value => {\n countMap[value] = (countMap[value] || 0) + 1;\n });\n \n // Update the counts, total, and uniqueCount\n setCounts(countMap);\n setTotal(valueArray.length);\n setUniqueCount(Object.keys(countMap).length);\n }, [simpleValue2, setCounts, setTotal, setUniqueCount]);\n \n // Handle adding a new value\n const handleAddValue = () => {\n if (!newValue.trim()) return;\n \n const updatedValues = [...(simpleValue2.values || []), newValue.trim()];\n setSimpleValue2({ ...simpleValue2, values: updatedValues });\n setNewValue(\"\");\n };\n \n // Handle removing a value\n const handleRemoveValue = (valueToRemove) => {\n const updatedValues = simpleValue2.values.filter(value => value !== valueToRemove);\n setSimpleValue2({ ...simpleValue2, values: updatedValues });\n };\n \n // Handle clearing all values\n const handleClearAll = () => {\n setSimpleValue2({ values: [] });\n };\n \n return (\n
\n

{title}

\n \n {/* Input section */}\n
\n

Add Values

\n
\n setNewValue(e.target.value)}\n className=\"flex-grow p-2 border rounded-l focus:outline-none focus:ring-2 focus:ring-blue-500\"\n placeholder=\"Enter a value\"\n />\n \n Add\n \n
\n \n
\n \n Clear All\n \n
\n
\n \n {/* Values list */}\n
\n
\n

Values List

\n {simpleValue2.values && simpleValue2.values.length > 0 ? (\n
    \n {simpleValue2.values.map((value, index) => (\n
  • \n {value}\n handleRemoveValue(value)}\n className=\"text-red-500 hover:text-red-700\"\n >\n ❌\n \n
  • \n ))}\n
\n ) : (\n

No values added yet

\n )}\n
\n \n {/* Counts display */}\n
\n

Value Counts

\n {Object.keys(counts).length > 0 ? (\n
    \n {Object.entries(counts).map(([value, count], index) => (\n
  • \n {value}\n \n {count}\n \n
  • \n ))}\n
\n ) : (\n

No counts available

\n )}\n
\n
\n \n {/* Summary statistics */}\n
\n

Summary

\n
\n
\n

Total Values

\n

{total}

\n
\n
\n

Unique Values

\n

{uniqueCount}

\n
\n
\n

Source

\n

{source}

\n
\n
\n
\n
\n );\n }\n\n // Render the component\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```" + } + ], + "stop": "\n```", + "stream": false +} \ No newline at end of file diff --git a/jumble/integration/rebuild-llm-cache.sh b/jumble/integration/rebuild-llm-cache.sh index 5b07987ef..57529f3f7 100755 --- a/jumble/integration/rebuild-llm-cache.sh +++ b/jumble/integration/rebuild-llm-cache.sh @@ -1,6 +1,20 @@ #!/bin/bash set -e # Exit on error +# Function to clean up processes +cleanup() { + echo "Cleaning up processes..." + if [ ! -z "$TOOLSHED_PID" ]; then + kill $TOOLSHED_PID 2>/dev/null || true + fi + if [ ! -z "$JUMBLE_PID" ]; then + kill $JUMBLE_PID 2>/dev/null || true + fi +} + +# Set up trap to call cleanup on script exit (success or failure) +trap cleanup EXIT + echo "Starting integration test environment..." # Ensure we're at the root of the repo @@ -12,15 +26,14 @@ elif [[ "$(basename "$(pwd)")" == "jumble" ]]; then cd .. fi -# 1. Delete existing cache -echo "Deleting existing LLM cache..." -rm -rf jumble/integration/cache/llm-api-cache -mkdir -p jumble/integration/cache/llm-api-cache +#1. Create temp cache directory +echo "Creating temp cache directory..." +TEMP_CACHE_DIR=$(mktemp -d) # 2. Start toolshed on port 8000 echo "Starting toolshed on port 8000..." cd toolshed -deno run dev & +CACHE_DIR=$TEMP_CACHE_DIR deno run dev & TOOLSHED_PID=$! cd .. @@ -47,15 +60,21 @@ cd .. # 5. Copy cache files echo "Copying LLM cache files to integration directory..." -mkdir -p integration/cache/llm-api-cache -cp -r toolshed/cache/llm-api-cache/* integration/cache/llm-api-cache/ -# 6. Clean up processes -echo "Cleaning up processes..." -kill $TOOLSHED_PID -kill $JUMBLE_PID +echo "List of fresh llm cache artifacts:" +ls -la $TEMP_CACHE_DIR/llm-api-cache + +# Ensure target directory exists +mkdir -p jumble/integration/cache/llm-api-cache + +# Copy files from temp cache to integration cache +cp -r $TEMP_CACHE_DIR/llm-api-cache/* jumble/integration/cache/llm-api-cache/ + +# Verify files were copied +echo "Verifying copied files:" +ls -la jumble/integration/cache/llm-api-cache/ -# 7. Print report and status +# 6. Print report and status echo "===============================================" echo "Integration test run complete!" echo "Cache files have been copied to integration/cache/llm-api-cache" diff --git a/jumble/integration/utils.ts b/jumble/integration/utils.ts index d0f99c43c..4d81201c3 100644 --- a/jumble/integration/utils.ts +++ b/jumble/integration/utils.ts @@ -108,7 +108,7 @@ export const waitForSelectorWithText = async ( selector: string, text: string, ): Promise => { - const retries = 30; + const retries = 60; const timeout = 1000; for (let i = 0; i < retries; i++) { const el = await page.waitForSelector(selector); diff --git a/jumble/src/views/CharmDetailView.tsx b/jumble/src/views/CharmDetailView.tsx index c83062581..e5dce87ef 100644 --- a/jumble/src/views/CharmDetailView.tsx +++ b/jumble/src/views/CharmDetailView.tsx @@ -61,7 +61,7 @@ const variantModels = [ "anthropic:claude-3-5-sonnet-latest", "anthropic:claude-3-7-sonnet-latest", "groq:llama-3.3-70b-versatile", - "google:gemini-2.0-pro", + "google:gemini-2.5-pro", ] as const; // =================== Context for Shared State =================== diff --git a/llm/src/client.ts b/llm/src/client.ts index 0e5f9fa01..7acbc2581 100644 --- a/llm/src/client.ts +++ b/llm/src/client.ts @@ -27,6 +27,7 @@ export type LLMRequest = { max_tokens?: number; stream?: boolean; stop?: string; + mode?: "json"; }; export class LLMClient { diff --git a/llm/src/prompts/json-gen.ts b/llm/src/prompts/json-gen.ts index 18518055d..262d6e6ae 100644 --- a/llm/src/prompts/json-gen.ts +++ b/llm/src/prompts/json-gen.ts @@ -62,6 +62,7 @@ export async function generateJSON( content: PROMPT, }, ], + mode: "json", }); const jsonString = parseTagFromResponse(response, "json_blob"); diff --git a/toolshed/deno.json b/toolshed/deno.json index 02d1395de..77cc581a5 100644 --- a/toolshed/deno.json +++ b/toolshed/deno.json @@ -9,9 +9,7 @@ }, "imports": { "@/": "./", - "@ai-sdk/amazon-bedrock": "npm:@ai-sdk/amazon-bedrock@^1.1.6", "@ai-sdk/anthropic": "npm:@ai-sdk/anthropic@^1.1.6", - "@ai-sdk/cerebras": "npm:@ai-sdk/cerebras@^0.1.8", "@ai-sdk/google-vertex": "npm:@ai-sdk/google-vertex@^2.1.12", "@std/cli": "jsr:@std/cli@^1.0.12", "gcp-metadata": "npm:gcp-metadata@6.1.0", diff --git a/toolshed/routes/ai/llm/generateText.test.ts b/toolshed/routes/ai/llm/generateText.test.ts new file mode 100644 index 000000000..96b176c29 --- /dev/null +++ b/toolshed/routes/ai/llm/generateText.test.ts @@ -0,0 +1,178 @@ +import { assertEquals } from "@std/assert"; +import { describe, it } from "@std/testing/bdd"; +import env from "@/env.ts"; +import { cleanJsonResponse, configureJsonMode } from "./generateText.ts"; + +if (env.ENV !== "test") { + throw new Error("ENV must be 'test'"); +} + +describe("configureJsonMode", () => { + it("configures JSON mode correctly for Groq models", () => { + const streamParams: Record = {}; + const messages = [{ + role: "user" as const, + content: "Generate a JSON response", + }]; + + configureJsonMode(streamParams, "groq:llama-3.3-70b", messages, false); + + assertEquals(streamParams.mode, undefined); + assertEquals(streamParams.response_format, { type: "json_object" }); + assertEquals(streamParams.providerOptions?.groq?.response_format, { + type: "json_object", + }); + assertEquals(typeof streamParams.system, "string"); + assertEquals( + streamParams.system.includes("respond with pure, correct JSON only"), + true, + ); + }); + + it("configures JSON mode correctly for OpenAI models", () => { + const streamParams: Record = {}; + const messages = [{ + role: "user" as const, + content: "Generate a JSON response", + }]; + + configureJsonMode(streamParams, "openai:gpt-4o", messages, false); + + assertEquals(streamParams.mode, undefined); + assertEquals(streamParams.response_format, { type: "json_object" }); + assertEquals(streamParams.providerOptions?.openai?.response_format, { + type: "json_object", + }); + }); + + it("configures JSON mode correctly for Anthropic models", () => { + const streamParams: Record = {}; + const messages = [{ + role: "user" as const, + content: "Generate a JSON response", + }]; + + configureJsonMode( + streamParams, + "anthropic:claude-3-7-sonnet", + messages, + false, + ); + + assertEquals(streamParams.mode, "json"); + assertEquals( + streamParams.system.includes("JSON generation assistant"), + true, + ); + assertEquals(streamParams.prefill?.text, "{\n"); + }); + + it("preserves existing system prompt while adding JSON instructions", () => { + const streamParams: Record = { + system: "You are an expert assistant.", + }; + const messages = [{ + role: "user" as const, + content: "Generate a JSON response", + }]; + + configureJsonMode( + streamParams, + "anthropic:claude-3-7-sonnet", + messages, + false, + ); + + assertEquals( + streamParams.system.includes( + "You are a JSON generation assistant. You are an expert assistant.", + ), + true, + ); + assertEquals( + streamParams.system.includes("response must be ONLY valid JSON"), + true, + ); + }); + + it("configures JSON mode correctly for other providers", () => { + const streamParams: Record = {}; + const messages = [{ + role: "user" as const, + content: "Generate a JSON response", + }]; + + configureJsonMode(streamParams, "other:model", messages, false); + + assertEquals(streamParams.mode, "json"); + assertEquals( + streamParams.system.includes("Ensure the response is valid JSON"), + true, + ); + }); + + it("adds JSON instructions to existing system prompt for other providers", () => { + const streamParams: Record = { + system: "You are an expert assistant.", + }; + const messages = [{ + role: "user" as const, + content: "Generate a JSON response", + }]; + + configureJsonMode(streamParams, "other:model", messages, false); + + assertEquals( + streamParams.system, + "You are an expert assistant.\nEnsure the response is valid JSON. DO NOT include any other text or formatting.", + ); + }); + + it("always adds JSON instructions even when system prompt already mentions JSON", () => { + const streamParams: Record = { + system: "You are an expert assistant who responds in JSON format.", + }; + const messages = [{ + role: "user" as const, + content: "Generate a JSON response", + }]; + + configureJsonMode(streamParams, "other:model", messages, false); + + // Should always add our JSON instructions + assertEquals( + streamParams.system, + "You are an expert assistant who responds in JSON format.\nEnsure the response is valid JSON. DO NOT include any other text or formatting.", + ); + }); +}); + +describe("cleanJsonResponse", () => { + it("extracts JSON from markdown code blocks", () => { + const input = '```json\n{"name": "Test", "value": 123}\n```'; + const expected = '{"name": "Test", "value": 123}'; + assertEquals(cleanJsonResponse(input), expected); + }); + + it("extracts JSON from code blocks without language specifier", () => { + const input = '```\n{"name": "Test", "value": 123}\n```'; + const expected = '{"name": "Test", "value": 123}'; + assertEquals(cleanJsonResponse(input), expected); + }); + + it("handles multiline JSON in code blocks", () => { + const input = '```json\n{\n "name": "Test",\n "value": 123\n}\n```'; + const expected = '{\n "name": "Test",\n "value": 123\n}'; + assertEquals(cleanJsonResponse(input), expected); + }); + + it("returns original text if no code blocks found", () => { + const input = '{"name": "Test", "value": 123}'; + assertEquals(cleanJsonResponse(input), input); + }); + + it("returns original text if code block format is incorrect", () => { + const input = '```json {"name": "Test", "value": 123}```'; + assertEquals(cleanJsonResponse(input), input); + }); +}); diff --git a/toolshed/routes/ai/llm/generateText.ts b/toolshed/routes/ai/llm/generateText.ts index 6017ca643..e1165b59b 100644 --- a/toolshed/routes/ai/llm/generateText.ts +++ b/toolshed/routes/ai/llm/generateText.ts @@ -2,6 +2,16 @@ import { streamText } from "ai"; import { findModel, TASK_MODELS } from "./models.ts"; +// Constants for JSON mode +const JSON_SYSTEM_PROMPTS = { + DEFAULT: + "Ensure the response is valid JSON. DO NOT include any other text or formatting.", + CLAUDE: + "You are a JSON generation assistant. Your task is to generate valid, properly formatted JSON according to the user's request. Follow these guidelines:\n\n1. Only output valid JSON - no other text, explanations, or markdown formatting\n2. Ensure all keys and string values are properly quoted with double quotes\n3. Maintain proper nesting and indentation\n4. Close all brackets and braces properly\n5. Use proper JSON syntax with commas between elements but not after the last element in arrays or objects\n\nYour entire response must be a single valid JSON object or array that could be directly parsed by JSON.parse().", + GROQ: + "You must respond with pure, correct JSON only - no text descriptions, no ```json code blocks, and no formatting outside of valid JSON. Your entire response should be a valid JSON object that can be parsed directly by JSON.parse() with no additional processing.", +}; + // Core generation logic separated from HTTP handling export interface GenerateTextParams { model?: string; @@ -12,13 +22,113 @@ export interface GenerateTextParams { stop_token?: string; abortSignal?: AbortSignal; max_tokens?: number; + mode?: "json"; + // Updated callback to receive complete data for caching + onStreamComplete?: (result: { + message: { role: "user" | "assistant"; content: string }; + messages: { role: "user" | "assistant"; content: string }[]; + originalRequest: GenerateTextParams; + }) => void; } export interface GenerateTextResult { message: { role: "user" | "assistant"; content: string }; + messages: { role: "user" | "assistant"; content: string }[]; stream?: ReadableStream; } +// Configure the model parameters for JSON mode based on provider +export function configureJsonMode( + streamParams: Record, + modelName: string, + messages: { role: "user" | "assistant"; content: string }[], + isStreaming: boolean, +): void { + // Default to using the generic JSON mode + streamParams.mode = "json"; + + // Apply provider-specific configurations + if (modelName?.startsWith("groq:")) { + // Groq uses response_format parameter + streamParams.response_format = { type: "json_object" }; + + // Ensure it's also passed through providerOptions for the Vercel AI SDK + streamParams.providerOptions = { + ...streamParams.providerOptions, + groq: { + response_format: { type: "json_object" }, + }, + }; + + // Add a stronger system prompt for Groq to prevent markdown code blocks + if (!streamParams.system) { + streamParams.system = JSON_SYSTEM_PROMPTS.GROQ; + } else { + streamParams.system = streamParams.system + "\n\n" + + JSON_SYSTEM_PROMPTS.GROQ; + } + + // Remove standard mode parameter as Groq doesn't support it + delete streamParams.mode; + } else if (modelName?.startsWith("openai:")) { + // OpenAI uses response_format parameter + streamParams.response_format = { type: "json_object" }; + + // Ensure it's also passed through providerOptions for the Vercel AI SDK + streamParams.providerOptions = { + ...streamParams.providerOptions, + openai: { + response_format: { type: "json_object" }, + }, + }; + + // Remove the mode parameter since OpenAI uses response_format instead + delete streamParams.mode; + } else if (modelName?.startsWith("anthropic:")) { + // Update or set system prompt for Claude + if (!streamParams.system) { + streamParams.system = JSON_SYSTEM_PROMPTS.CLAUDE; + } else { + // Prepend the JSON assistant role and append the JSON-specific instructions + streamParams.system = "You are a JSON generation assistant. " + + streamParams.system + + "\n\nImportant: Your response must be ONLY valid JSON - no other text, explanations, or markdown formatting. The output should be directly parseable by JSON.parse()."; + } + + // Use prefill for non-streaming responses to anchor the JSON structure + if ( + !isStreaming && messages.length > 0 && + messages[messages.length - 1].role === "user" + ) { + streamParams.prefill = { + text: "{\n", + }; + } + } else { + // For other providers, set a standard system prompt if one isn't provided + if (!streamParams.system) { + streamParams.system = JSON_SYSTEM_PROMPTS.DEFAULT; + } else { + // Always append JSON instructions, even if the prompt already mentions JSON + streamParams.system += "\n" + JSON_SYSTEM_PROMPTS.DEFAULT; + } + } +} + +// Add a helper function to clean up JSON responses from markdown code blocks +export function cleanJsonResponse(text: string): string { + // Check if the response is wrapped in markdown code blocks + const jsonCodeBlockRegex = /```(json)?\s*\n([\s\S]*?)\n```/; + const match = text.match(jsonCodeBlockRegex); + + if (match && match[2]) { + // Return just the JSON content inside the code block + return match[2].trim(); + } + + return text; +} + export async function generateText( params: GenerateTextParams, ): Promise { @@ -45,8 +155,13 @@ export async function generateText( throw new Error(`Unsupported model: ${modelName}`); } + // Groq models don't support streaming in JSON mode + if (params.mode && params.stream && modelName?.startsWith("groq:")) { + throw new Error("Groq models don't support streaming in JSON mode"); + } + const messages = params.messages; - const streamParams = { + const streamParams: Record = { model: modelConfig.model || modelName!, messages, stream: params.stream, @@ -57,6 +172,16 @@ export async function generateText( maxTokens: params.max_tokens, }; + // Apply JSON mode configuration if requested + if (params.mode) { + configureJsonMode( + streamParams, + modelName!, + messages, + params.stream || false, + ); + } + // Handle models that don't support system prompts if ( !modelConfig.capabilities.systemPrompt && params.system && @@ -84,7 +209,16 @@ export async function generateText( throw new Error("No response from LLM"); } - if ((await llmStream.finishReason) === "stop" && params.stop_token) { + // Clean up JSON responses when mode is enabled + if (params.mode) { + result = cleanJsonResponse(result); + } + + // Only add stop token if not in JSON mode to avoid breaking JSON structure + if ( + (await llmStream.finishReason) === "stop" && params.stop_token && + !params.mode + ) { result += params.stop_token; } @@ -94,7 +228,10 @@ export async function generateText( messages[messages.length - 1].content = result; } - return { message: messages[messages.length - 1] }; + return { + message: messages[messages.length - 1], + messages: [...messages], + }; } // Create streaming response @@ -117,14 +254,22 @@ export async function generateText( ); } - // Add stop sequence if specified - if ((await llmStream.finishReason) === "stop" && params.stop_token) { + // Only add stop token if not in JSON mode to avoid breaking JSON structure + if ( + (await llmStream.finishReason) === "stop" && params.stop_token && + !params.mode + ) { result += params.stop_token; controller.enqueue( new TextEncoder().encode(JSON.stringify(params.stop_token) + "\n"), ); } + // For JSON mode, clean the result to strip any markdown code blocks + if (params.mode) { + result = cleanJsonResponse(result); + } + // Update message history if (messages[messages.length - 1].role === "user") { messages.push({ role: "assistant", content: result }); @@ -132,12 +277,22 @@ export async function generateText( messages[messages.length - 1].content = result; } + // Call the onStreamComplete callback with all the data needed for caching + if (params.onStreamComplete) { + params.onStreamComplete({ + message: messages[messages.length - 1], + messages: [...messages], + originalRequest: params, + }); + } + controller.close(); }, }); return { message: messages[messages.length - 1], + messages: [...messages], stream, }; } diff --git a/toolshed/routes/ai/llm/llm.handlers.ts b/toolshed/routes/ai/llm/llm.handlers.ts index b131c5ac0..76edeb251 100644 --- a/toolshed/routes/ai/llm/llm.handlers.ts +++ b/toolshed/routes/ai/llm/llm.handlers.ts @@ -6,6 +6,42 @@ import * as cache from "./cache.ts"; import type { Context } from "@hono/hono"; import { generateText as generateTextCore } from "./generateText.ts"; import { findModel } from "./models.ts"; + +/** + * Validates that the model and JSON mode settings are compatible + * @returns An error response object if validation fails, or null if validation passes + */ +function validateModelAndJsonMode( + c: Context, + modelString: string | undefined, + mode: string | undefined, +) { + const model = modelString ? findModel(modelString) : null; + + if (!model) { + return c.json({ error: "Invalid model" }, HttpStatusCodes.BAD_REQUEST); + } + + // Validate JSON mode support if requested + const isJsonMode = mode === "json"; + + // Groq models don't support streaming with JSON mode + if ( + isJsonMode && c.req.query("stream") === "true" && + modelString?.startsWith("groq:") + ) { + return c.json( + { + error: + "Groq models don't support streaming in JSON mode. Please set stream to false.", + }, + HttpStatusCodes.BAD_REQUEST, + ); + } + + return null; +} + /** * Handler for GET /models endpoint * Returns filtered list of available LLM models based on search criteria @@ -56,34 +92,55 @@ export const getModels: AppRouteHandler = (c) => { export const generateText: AppRouteHandler = async (c) => { const payload = await c.req.json(); - const modelString = payload.model; - const model = modelString ? findModel(modelString) : null; - const modelDefaultMaxTokens = model?.capabilities.maxOutputTokens || 8000; - if (!model) { - return c.json({ error: "Invalid model" }, HttpStatusCodes.BAD_REQUEST); + // First, check whether the request is cached, if so return the cached result + const cacheKey = await cache.hashKey(JSON.stringify(payload)); + const cachedResult = await cache.loadItem(cacheKey); + if (cachedResult) { + const lastMessage = cachedResult.messages[cachedResult.messages.length - 1]; + return c.json(lastMessage); } - try { - // Check cache for existing response - const cacheKey = await cache.hashKey(JSON.stringify(payload)); - const cachedResult = await cache.loadItem(cacheKey); - if (cachedResult) { - const lastMessage = - cachedResult.messages[cachedResult.messages.length - 1]; - return c.json(lastMessage); + const persistCache = async ( + messages: { role: string; content: string }[], + ) => { + try { + await cache.saveItem(cacheKey, { + ...payload, + messages, + }); + } catch (e) { + console.error("Error saving response to cache:", e); } + }; + + const validationError = validateModelAndJsonMode( + c, + payload.model, + payload.mode, + ); + if (validationError) { + return validationError; + } + + const model = findModel(payload.model); + const modelDefaultMaxTokens = model?.capabilities.maxOutputTokens || 8000; + try { const result = await generateTextCore({ ...payload, abortSignal: c.req.raw.signal, max_tokens: payload.max_tokens || modelDefaultMaxTokens, + // If response is streaming, save to cache after the stream is complete + onStreamComplete: payload.stream + ? async (result) => { + await persistCache(result.messages); + } + : undefined, }); + // If response is not streaming, save to cache and return the message if (!payload.stream) { - await cache.saveItem(cacheKey, { - ...payload, - messages: [...payload.messages, result.message], - }); + await persistCache(result.messages); return c.json(result.message); } diff --git a/toolshed/routes/ai/llm/llm.routes.ts b/toolshed/routes/ai/llm/llm.routes.ts index 25996f442..93e26220e 100644 --- a/toolshed/routes/ai/llm/llm.routes.ts +++ b/toolshed/routes/ai/llm/llm.routes.ts @@ -23,6 +23,7 @@ export const LLMRequestSchema = z.object({ stop_token: z.string().optional(), max_completion_tokens: z.number().optional(), stream: z.boolean().default(false), + mode: z.enum(["json"]).optional(), }); export const ModelInfoSchema = z.object({ @@ -111,7 +112,7 @@ export const generateText = createRoute({ "application/json": { schema: LLMRequestSchema.openapi({ example: { - model: "claude-3-5-sonnet", + model: "anthropic:claude-3-7-sonnet-latest", system: "You are a pirate, make sure you talk like one.", stream: false, messages: [ diff --git a/toolshed/routes/ai/llm/models.ts b/toolshed/routes/ai/llm/models.ts index b20b1f4eb..c62a7674b 100644 --- a/toolshed/routes/ai/llm/models.ts +++ b/toolshed/routes/ai/llm/models.ts @@ -3,7 +3,6 @@ import { createOpenAI } from "@ai-sdk/openai"; import { createGroq, groq } from "@ai-sdk/groq"; import { openai } from "@ai-sdk/openai"; import { createVertex, vertex } from "@ai-sdk/google-vertex"; -import { cerebras, createCerebras } from "@ai-sdk/cerebras"; import env from "@/env.ts"; @@ -50,8 +49,7 @@ const addModel = ({ | typeof anthropic | typeof groq | typeof openai - | typeof vertex - | typeof cerebras; + | typeof vertex; name: string; aliases: string[]; capabilities: Capabilities; @@ -92,20 +90,6 @@ if (env.CTTS_AI_LLM_ANTHROPIC_API_KEY) { const anthropicProvider = createAnthropic({ apiKey: env.CTTS_AI_LLM_ANTHROPIC_API_KEY, }); - addModel({ - provider: anthropicProvider, - name: "anthropic:claude-3-5-haiku-20241022", - aliases: ["anthropic:claude-3-5-haiku-latest", "claude-3-5-haiku"], - capabilities: { - contextWindow: 200_000, - maxOutputTokens: 8192, - images: true, - prefill: true, - systemPrompt: true, - stopSequences: true, - streaming: true, - }, - }); addModel({ provider: anthropicProvider, @@ -158,21 +142,6 @@ if (env.CTTS_AI_LLM_ANTHROPIC_API_KEY) { }, }, }); - - addModel({ - provider: anthropicProvider, - name: "anthropic:claude-3-opus-20240229", - aliases: ["anthropic:claude-3-opus-latest", "claude-3-opus"], - capabilities: { - contextWindow: 200_000, - maxOutputTokens: 4096, - images: true, - prefill: true, - systemPrompt: true, - stopSequences: true, - streaming: true, - }, - }); } if (env.CTTS_AI_LLM_GROQ_API_KEY) { @@ -253,21 +222,6 @@ if (env.CTTS_AI_LLM_GROQ_API_KEY) { streaming: false, }, }); - - addModel({ - provider: groqProvider, - name: "groq:llama-3.2-90b-vision-preview", - aliases: ["groq:llama-3.2-90b-vision", "llama-3.2-90b-vision"], - capabilities: { - contextWindow: 128_000, - maxOutputTokens: 8000, - images: true, - prefill: true, - systemPrompt: true, - stopSequences: true, - streaming: false, - }, - }); } if (env.CTTS_AI_LLM_OPENAI_API_KEY) { @@ -411,66 +365,6 @@ if (env.CTTS_AI_LLM_GOOGLE_APPLICATION_CREDENTIALS) { location: env.CTTS_AI_LLM_GOOGLE_VERTEX_LOCATION, }); - addModel({ - provider: vertexProvider, - name: "google:gemini-2.0-flash", - aliases: ["google:gemini-2.0-flash", "gemini-2.0-flash"], - capabilities: { - contextWindow: 1_048_576, - maxOutputTokens: 8192, - images: true, - prefill: true, - systemPrompt: true, - stopSequences: true, - streaming: true, - }, - }); - - addModel({ - provider: vertexProvider, - name: "google:gemini-2.0-flash-lite-preview-02-05", - aliases: ["google:gemini-2.0-flash-lite", "gemini-2.0-flash-lite"], - capabilities: { - contextWindow: 1_048_576, - maxOutputTokens: 8192, - images: true, - prefill: true, - systemPrompt: true, - stopSequences: true, - streaming: true, - }, - }); - - addModel({ - provider: vertexProvider, - name: "google:gemini-2.0-flash-thinking-exp-01-21", - aliases: ["google:gemini-2.0-flash-thinking", "gemini-2.0-flash-thinking"], - capabilities: { - contextWindow: 1_048_576, - maxOutputTokens: 8192, - images: true, - prefill: true, - systemPrompt: true, - stopSequences: true, - streaming: true, - }, - }); - - addModel({ - provider: vertexProvider, - name: "google:gemini-2.0-pro-exp-02-05", - aliases: ["google:gemini-2.0-pro", "gemini-2.0-pro"], - capabilities: { - contextWindow: 2_097_152, - maxOutputTokens: 8192, - images: true, - prefill: true, - systemPrompt: true, - stopSequences: true, - streaming: true, - }, - }); - addModel({ provider: vertexProvider, name: "google:gemini-2.5-pro-exp-03-25", @@ -487,26 +381,6 @@ if (env.CTTS_AI_LLM_GOOGLE_APPLICATION_CREDENTIALS) { }); } -if (env.CTTS_AI_LLM_CEREBRAS_API_KEY) { - const cerebrasProvider = createCerebras({ - apiKey: env.CTTS_AI_LLM_CEREBRAS_API_KEY, - }); - addModel({ - provider: cerebrasProvider, - name: "cerebras:llama-3.3-70b", - aliases: ["cerebras"], - capabilities: { - contextWindow: 8192, - maxOutputTokens: 8192, - images: false, - prefill: false, - systemPrompt: true, - stopSequences: true, - streaming: true, - }, - }); -} - if (env.CTTS_AI_LLM_PERPLEXITY_API_KEY) { const perplexityProvider = createOpenAI({ name: "perplexity", @@ -543,74 +417,8 @@ if (env.CTTS_AI_LLM_PERPLEXITY_API_KEY) { streaming: true, }, }); - - addModel({ - provider: perplexityProvider, - name: "perplexity:sonar", - aliases: ["sonar"], - capabilities: { - contextWindow: 127_000, - maxOutputTokens: 8000, - images: false, - prefill: false, - systemPrompt: false, - stopSequences: true, - streaming: true, - }, - }); } -// FIXME(jake): There's some package import error with the bedrock provider. Commenting out for now. -// if ( -// env.CTTS_AI_LLM_AWS_ACCESS_KEY_ID && -// env.CTTS_AI_LLM_AWS_SECRET_ACCESS_KEY -// ) { -// addModel({ -// provider: bedrock, -// name: "us.amazon.nova-micro-v1:0", -// aliases: ["amazon:nova-micro", "nova-micro"], -// capabilities: { -// contextWindow: 128_000, -// maxOutputTokens: 5000, -// images: false, -// prefill: true, -// systemPrompt: true, -// stopSequences: true, -// streaming: true, -// }, -// }); - -// addModel({ -// provider: bedrock, -// name: "us.amazon.nova-lite-v1:0", -// aliases: ["amazon:nova-lite", "nova-lite"], -// capabilities: { -// contextWindow: 300_000, -// maxOutputTokens: 5000, -// images: true, -// prefill: true, -// systemPrompt: true, -// stopSequences: true, -// streaming: true, -// }, -// }); - -// addModel({ -// provider: bedrock, -// name: "us.amazon.nova-pro-v1:0", -// aliases: ["amazon:nova-pro", "nova-pro"], -// capabilities: { -// contextWindow: 300_000, -// maxOutputTokens: 5000, -// images: true, -// prefill: true, -// systemPrompt: true, -// stopSequences: true, -// streaming: true, -// }, -// }); -// } - export const findModel = (name: string) => { return MODELS[name]; };