codeply-cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/codeply.js +125 -14
- package/lib/agent.mjs +157 -18
- package/lib/ai.js +221 -15
- package/lib/apply-limit.js +13 -3
- package/lib/config.js +63 -16
- package/lib/edit-engine.js +1 -1
- package/lib/skills.js +358 -0
- package/lib/tools.mjs +63 -6
- package/lib/tui.mjs +96 -3
- package/package.json +3 -2
- package/skills/SOURCE.md +58 -0
- package/skills/accessibility/SKILL.md +147 -0
- package/skills/agent-architecture-audit/SKILL.md +257 -0
- package/skills/agent-eval/SKILL.md +147 -0
- package/skills/agent-harness-construction/SKILL.md +74 -0
- package/skills/agent-introspection-debugging/SKILL.md +154 -0
- package/skills/agent-payment-x402/SKILL.md +225 -0
- package/skills/agent-self-evaluation/SKILL.md +182 -0
- package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -0
- package/skills/agent-self-evaluation/references/hook-integration.md +64 -0
- package/skills/agent-sort/SKILL.md +216 -0
- package/skills/agentic-engineering/SKILL.md +64 -0
- package/skills/agentic-os/SKILL.md +388 -0
- package/skills/ai-first-engineering/SKILL.md +52 -0
- package/skills/ai-regression-testing/SKILL.md +386 -0
- package/skills/android-clean-architecture/SKILL.md +340 -0
- package/skills/angular-developer/SKILL.md +155 -0
- package/skills/angular-developer/references/angular-animations.md +160 -0
- package/skills/angular-developer/references/angular-aria.md +410 -0
- package/skills/angular-developer/references/cli.md +86 -0
- package/skills/angular-developer/references/component-harnesses.md +59 -0
- package/skills/angular-developer/references/component-styling.md +91 -0
- package/skills/angular-developer/references/components.md +117 -0
- package/skills/angular-developer/references/creating-services.md +97 -0
- package/skills/angular-developer/references/data-resolvers.md +69 -0
- package/skills/angular-developer/references/define-routes.md +67 -0
- package/skills/angular-developer/references/defining-providers.md +72 -0
- package/skills/angular-developer/references/di-fundamentals.md +120 -0
- package/skills/angular-developer/references/e2e-testing.md +56 -0
- package/skills/angular-developer/references/effects.md +83 -0
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
- package/skills/angular-developer/references/host-elements.md +80 -0
- package/skills/angular-developer/references/injection-context.md +63 -0
- package/skills/angular-developer/references/inputs.md +101 -0
- package/skills/angular-developer/references/linked-signal.md +59 -0
- package/skills/angular-developer/references/loading-strategies.md +61 -0
- package/skills/angular-developer/references/mcp.md +108 -0
- package/skills/angular-developer/references/navigate-to-routes.md +69 -0
- package/skills/angular-developer/references/outputs.md +86 -0
- package/skills/angular-developer/references/reactive-forms.md +122 -0
- package/skills/angular-developer/references/rendering-strategies.md +44 -0
- package/skills/angular-developer/references/resource.md +77 -0
- package/skills/angular-developer/references/route-animations.md +56 -0
- package/skills/angular-developer/references/route-guards.md +52 -0
- package/skills/angular-developer/references/router-lifecycle.md +45 -0
- package/skills/angular-developer/references/router-testing.md +87 -0
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
- package/skills/angular-developer/references/signal-forms.md +795 -0
- package/skills/angular-developer/references/signals-overview.md +94 -0
- package/skills/angular-developer/references/tailwind-css.md +69 -0
- package/skills/angular-developer/references/template-driven-forms.md +114 -0
- package/skills/angular-developer/references/testing-fundamentals.md +65 -0
- package/skills/api-connector-builder/SKILL.md +121 -0
- package/skills/api-design/SKILL.md +524 -0
- package/skills/architecture-decision-records/SKILL.md +180 -0
- package/skills/article-writing/SKILL.md +80 -0
- package/skills/automation-audit-ops/SKILL.md +143 -0
- package/skills/autonomous-agent-harness/SKILL.md +274 -0
- package/skills/autonomous-loops/SKILL.md +611 -0
- package/skills/backend-patterns/SKILL.md +562 -0
- package/skills/benchmark/SKILL.md +95 -0
- package/skills/benchmark-methodology/SKILL.md +191 -0
- package/skills/benchmark-optimization-loop/SKILL.md +71 -0
- package/skills/blender-motion-state-inspection/SKILL.md +165 -0
- package/skills/blueprint/SKILL.md +106 -0
- package/skills/brand-discovery/SKILL.md +145 -0
- package/skills/brand-discovery/references/10_purpose-why.md +40 -0
- package/skills/brand-discovery/references/20_positioning.md +44 -0
- package/skills/brand-discovery/references/30_audience-niche.md +52 -0
- package/skills/brand-discovery/references/40_personality-archetype.md +57 -0
- package/skills/brand-discovery/references/50_voice-tone.md +59 -0
- package/skills/brand-discovery/references/60_narrative-story.md +50 -0
- package/skills/brand-discovery/references/70_founder-tension.md +49 -0
- package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -0
- package/skills/brand-voice/SKILL.md +98 -0
- package/skills/brand-voice/references/voice-profile-schema.md +55 -0
- package/skills/browser-qa/SKILL.md +105 -0
- package/skills/bun-runtime/SKILL.md +85 -0
- package/skills/canary-watch/SKILL.md +108 -0
- package/skills/carrier-relationship-management/SKILL.md +212 -0
- package/skills/cisco-ios-patterns/SKILL.md +164 -0
- package/skills/ck/SKILL.md +148 -0
- package/skills/claude-devfleet/SKILL.md +112 -0
- package/skills/click-path-audit/SKILL.md +245 -0
- package/skills/clickhouse-io/SKILL.md +445 -0
- package/skills/code-tour/SKILL.md +254 -0
- package/skills/codebase-onboarding/SKILL.md +234 -0
- package/skills/codehealth-mcp/SKILL.md +167 -0
- package/skills/coding-standards/SKILL.md +551 -0
- package/skills/competitive-platform-analysis/SKILL.md +214 -0
- package/skills/competitive-report-structure/SKILL.md +162 -0
- package/skills/compose-multiplatform-patterns/SKILL.md +300 -0
- package/skills/config-gc/SKILL.md +120 -0
- package/skills/configure-ecc/SKILL.md +385 -0
- package/skills/connections-optimizer/SKILL.md +190 -0
- package/skills/content-engine/SKILL.md +132 -0
- package/skills/content-hash-cache-pattern/SKILL.md +162 -0
- package/skills/context-budget/SKILL.md +136 -0
- package/skills/continuous-agent-loop/SKILL.md +46 -0
- package/skills/continuous-learning/SKILL.md +132 -0
- package/skills/continuous-learning-v2/SKILL.md +361 -0
- package/skills/contract-first/SKILL.md +287 -0
- package/skills/cost-aware-llm-pipeline/SKILL.md +184 -0
- package/skills/cost-tracking/SKILL.md +97 -0
- package/skills/council/SKILL.md +204 -0
- package/skills/cpp-coding-standards/SKILL.md +724 -0
- package/skills/cpp-testing/SKILL.md +325 -0
- package/skills/crosspost/SKILL.md +112 -0
- package/skills/csharp-testing/SKILL.md +322 -0
- package/skills/customer-billing-ops/SKILL.md +141 -0
- package/skills/customs-trade-compliance/SKILL.md +263 -0
- package/skills/dart-flutter-patterns/SKILL.md +564 -0
- package/skills/dashboard-builder/SKILL.md +109 -0
- package/skills/data-scraper-agent/SKILL.md +765 -0
- package/skills/data-throughput-accelerator/SKILL.md +74 -0
- package/skills/database-migrations/SKILL.md +430 -0
- package/skills/deep-research/SKILL.md +160 -0
- package/skills/defi-amm-security/SKILL.md +167 -0
- package/skills/delivery-gate/SKILL.md +126 -0
- package/skills/deployment-patterns/SKILL.md +428 -0
- package/skills/design-system/SKILL.md +83 -0
- package/skills/django-celery/SKILL.md +458 -0
- package/skills/django-patterns/SKILL.md +735 -0
- package/skills/django-security/SKILL.md +644 -0
- package/skills/django-tdd/SKILL.md +730 -0
- package/skills/django-verification/SKILL.md +470 -0
- package/skills/dmux-workflows/SKILL.md +192 -0
- package/skills/docker-patterns/SKILL.md +365 -0
- package/skills/documentation-lookup/SKILL.md +91 -0
- package/skills/dotnet-patterns/SKILL.md +322 -0
- package/skills/dynamic-workflow-mode/SKILL.md +124 -0
- package/skills/e2e-testing/SKILL.md +327 -0
- package/skills/ecc-guide/SKILL.md +190 -0
- package/skills/ecc-recipes/SKILL.md +149 -0
- package/skills/ecc-tools-cost-audit/SKILL.md +161 -0
- package/skills/email-ops/SKILL.md +122 -0
- package/skills/energy-procurement/SKILL.md +228 -0
- package/skills/enterprise-agent-ops/SKILL.md +51 -0
- package/skills/error-handling/SKILL.md +377 -0
- package/skills/eval-harness/SKILL.md +271 -0
- package/skills/evm-token-decimals/SKILL.md +131 -0
- package/skills/exa-search/SKILL.md +108 -0
- package/skills/fal-ai-media/SKILL.md +289 -0
- package/skills/fastapi-patterns/SKILL.md +514 -0
- package/skills/finance-billing-ops/SKILL.md +128 -0
- package/skills/flox-environments/SKILL.md +497 -0
- package/skills/flutter-dart-code-review/SKILL.md +436 -0
- package/skills/foundation-models-on-device/SKILL.md +243 -0
- package/skills/frontend-a11y/SKILL.md +446 -0
- package/skills/frontend-design-direction/SKILL.md +93 -0
- package/skills/frontend-patterns/SKILL.md +657 -0
- package/skills/frontend-slides/SKILL.md +185 -0
- package/skills/fsharp-testing/SKILL.md +281 -0
- package/skills/gan-style-harness/SKILL.md +279 -0
- package/skills/gateguard/SKILL.md +133 -0
- package/skills/generating-python-installer/SKILL.md +820 -0
- package/skills/git-workflow/SKILL.md +716 -0
- package/skills/github-ops/SKILL.md +145 -0
- package/skills/golang-patterns/SKILL.md +676 -0
- package/skills/golang-testing/SKILL.md +721 -0
- package/skills/google-workspace-ops/SKILL.md +96 -0
- package/skills/growth-log/SKILL.md +128 -0
- package/skills/healthcare-cdss-patterns/SKILL.md +246 -0
- package/skills/healthcare-emr-patterns/SKILL.md +160 -0
- package/skills/healthcare-eval-harness/SKILL.md +208 -0
- package/skills/healthcare-phi-compliance/SKILL.md +146 -0
- package/skills/hermes-imports/SKILL.md +89 -0
- package/skills/hexagonal-architecture/SKILL.md +277 -0
- package/skills/hipaa-compliance/SKILL.md +79 -0
- package/skills/homelab-network-readiness/SKILL.md +170 -0
- package/skills/homelab-network-setup/SKILL.md +130 -0
- package/skills/homelab-pihole-dns/SKILL.md +275 -0
- package/skills/homelab-vlan-segmentation/SKILL.md +312 -0
- package/skills/homelab-wireguard-vpn/SKILL.md +306 -0
- package/skills/hookify-rules/SKILL.md +128 -0
- package/skills/inherit-legacy-style/SKILL.md +157 -0
- package/skills/intent-driven-development/SKILL.md +360 -0
- package/skills/inventory-demand-planning/SKILL.md +247 -0
- package/skills/investor-materials/SKILL.md +97 -0
- package/skills/investor-outreach/SKILL.md +92 -0
- package/skills/ios-icon-gen/SKILL.md +158 -0
- package/skills/iterative-retrieval/SKILL.md +212 -0
- package/skills/ito-basket-compare/SKILL.md +64 -0
- package/skills/ito-compute/SKILL.md +129 -0
- package/skills/ito-data-atlas-agent/SKILL.md +64 -0
- package/skills/ito-market-intelligence/SKILL.md +61 -0
- package/skills/ito-trade-planner/SKILL.md +68 -0
- package/skills/java-coding-standards/SKILL.md +384 -0
- package/skills/jira-integration/SKILL.md +303 -0
- package/skills/jpa-patterns/SKILL.md +152 -0
- package/skills/knowledge-ops/SKILL.md +155 -0
- package/skills/kotlin-coroutines-flows/SKILL.md +285 -0
- package/skills/kotlin-exposed-patterns/SKILL.md +720 -0
- package/skills/kotlin-ktor-patterns/SKILL.md +690 -0
- package/skills/kotlin-patterns/SKILL.md +712 -0
- package/skills/kotlin-testing/SKILL.md +825 -0
- package/skills/kubernetes-patterns/SKILL.md +756 -0
- package/skills/laravel-patterns/SKILL.md +416 -0
- package/skills/laravel-plugin-discovery/SKILL.md +230 -0
- package/skills/laravel-security/SKILL.md +948 -0
- package/skills/laravel-tdd/SKILL.md +675 -0
- package/skills/laravel-verification/SKILL.md +180 -0
- package/skills/latency-critical-systems/SKILL.md +75 -0
- package/skills/lead-intelligence/SKILL.md +322 -0
- package/skills/liquid-glass-design/SKILL.md +279 -0
- package/skills/llm-trading-agent-security/SKILL.md +147 -0
- package/skills/logistics-exception-management/SKILL.md +222 -0
- package/skills/loop-design-check/SKILL.md +143 -0
- package/skills/mailtrap-email-integration/SKILL.md +77 -0
- package/skills/make-interfaces-feel-better/SKILL.md +152 -0
- package/skills/manim-video/SKILL.md +90 -0
- package/skills/market-research/SKILL.md +76 -0
- package/skills/marketing-campaign/SKILL.md +114 -0
- package/skills/mcp-server-patterns/SKILL.md +70 -0
- package/skills/messages-ops/SKILL.md +105 -0
- package/skills/ml-adoption-playbook/SKILL.md +57 -0
- package/skills/mle-workflow/SKILL.md +348 -0
- package/skills/motion-advanced/SKILL.md +596 -0
- package/skills/motion-foundations/SKILL.md +299 -0
- package/skills/motion-patterns/SKILL.md +434 -0
- package/skills/motion-ui/SKILL.md +576 -0
- package/skills/mysql-patterns/SKILL.md +413 -0
- package/skills/nanoclaw-repl/SKILL.md +34 -0
- package/skills/nestjs-patterns/SKILL.md +231 -0
- package/skills/netmiko-ssh-automation/SKILL.md +174 -0
- package/skills/network-bgp-diagnostics/SKILL.md +168 -0
- package/skills/network-config-validation/SKILL.md +211 -0
- package/skills/network-interface-health/SKILL.md +153 -0
- package/skills/nextjs-turbopack/SKILL.md +58 -0
- package/skills/nodejs-keccak256/SKILL.md +103 -0
- package/skills/nutrient-document-processing/SKILL.md +168 -0
- package/skills/nuxt4-patterns/SKILL.md +101 -0
- package/skills/openclaw-persona-forge/SKILL.md +289 -0
- package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
- package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
- package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
- package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
- package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
- package/skills/openclaw-persona-forge/references/output-template.md +166 -0
- package/skills/opensource-pipeline/SKILL.md +256 -0
- package/skills/orch-add-feature/SKILL.md +45 -0
- package/skills/orch-build-mvp/SKILL.md +49 -0
- package/skills/orch-change-feature/SKILL.md +43 -0
- package/skills/orch-fix-defect/SKILL.md +43 -0
- package/skills/orch-pipeline/SKILL.md +121 -0
- package/skills/orch-refine-code/SKILL.md +44 -0
- package/skills/parallel-execution-optimizer/SKILL.md +74 -0
- package/skills/perl-patterns/SKILL.md +505 -0
- package/skills/perl-security/SKILL.md +504 -0
- package/skills/perl-testing/SKILL.md +476 -0
- package/skills/plan-canvas/SKILL.md +153 -0
- package/skills/plan-orchestrate/SKILL.md +263 -0
- package/skills/plankton-code-quality/SKILL.md +237 -0
- package/skills/postgres-patterns/SKILL.md +148 -0
- package/skills/prediction-market-oracle-research/SKILL.md +64 -0
- package/skills/prediction-market-risk-review/SKILL.md +61 -0
- package/skills/prisma-patterns/SKILL.md +401 -0
- package/skills/product-capability/SKILL.md +142 -0
- package/skills/product-lens/SKILL.md +93 -0
- package/skills/production-audit/SKILL.md +207 -0
- package/skills/production-scheduling/SKILL.md +238 -0
- package/skills/project-flow-ops/SKILL.md +112 -0
- package/skills/prompt-optimizer/SKILL.md +398 -0
- package/skills/python-patterns/SKILL.md +751 -0
- package/skills/python-testing/SKILL.md +817 -0
- package/skills/pytorch-patterns/SKILL.md +397 -0
- package/skills/quality-nonconformance/SKILL.md +260 -0
- package/skills/quarkus-patterns/SKILL.md +723 -0
- package/skills/quarkus-security/SKILL.md +468 -0
- package/skills/quarkus-tdd/SKILL.md +812 -0
- package/skills/quarkus-verification/SKILL.md +480 -0
- package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -0
- package/skills/react-native-patterns/SKILL.md +326 -0
- package/skills/react-patterns/SKILL.md +342 -0
- package/skills/react-performance/SKILL.md +575 -0
- package/skills/react-testing/SKILL.md +424 -0
- package/skills/recsys-pipeline-architect/SKILL.md +115 -0
- package/skills/recursive-decision-ledger/SKILL.md +81 -0
- package/skills/redis-patterns/SKILL.md +404 -0
- package/skills/regex-vs-llm-structured-text/SKILL.md +221 -0
- package/skills/remotion-video-creation/SKILL.md +43 -0
- package/skills/repo-scan/SKILL.md +79 -0
- package/skills/research-ops/SKILL.md +113 -0
- package/skills/returns-reverse-logistics/SKILL.md +240 -0
- package/skills/rules-distill/SKILL.md +265 -0
- package/skills/rust-patterns/SKILL.md +500 -0
- package/skills/rust-testing/SKILL.md +501 -0
- package/skills/safety-guard/SKILL.md +76 -0
- package/skills/santa-method/SKILL.md +307 -0
- package/skills/scientific-db-pubmed-database/SKILL.md +176 -0
- package/skills/scientific-db-uspto-database/SKILL.md +178 -0
- package/skills/scientific-pkg-gget/SKILL.md +167 -0
- package/skills/scientific-thinking-literature-review/SKILL.md +193 -0
- package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -0
- package/skills/search-first/SKILL.md +183 -0
- package/skills/security-bounty-hunter/SKILL.md +100 -0
- package/skills/security-review/SKILL.md +504 -0
- package/skills/security-scan/SKILL.md +166 -0
- package/skills/seo/SKILL.md +155 -0
- package/skills/skill-comply/SKILL.md +59 -0
- package/skills/skill-scout/SKILL.md +141 -0
- package/skills/skill-stocktake/SKILL.md +195 -0
- package/skills/social-graph-ranker/SKILL.md +155 -0
- package/skills/social-publisher/SKILL.md +130 -0
- package/skills/springboot-patterns/SKILL.md +315 -0
- package/skills/springboot-security/SKILL.md +273 -0
- package/skills/springboot-tdd/SKILL.md +159 -0
- package/skills/springboot-verification/SKILL.md +232 -0
- package/skills/strategic-compact/SKILL.md +142 -0
- package/skills/swift-actor-persistence/SKILL.md +144 -0
- package/skills/swift-concurrency-6-2/SKILL.md +216 -0
- package/skills/swift-protocol-di-testing/SKILL.md +191 -0
- package/skills/swiftui-patterns/SKILL.md +259 -0
- package/skills/taste/SKILL.md +264 -0
- package/skills/taste/references/genre-taxonomy.md +87 -0
- package/skills/tdd-workflow/SKILL.md +583 -0
- package/skills/team-agent-orchestration/SKILL.md +111 -0
- package/skills/team-builder/SKILL.md +169 -0
- package/skills/terminal-ops/SKILL.md +110 -0
- package/skills/tinystruct-patterns/SKILL.md +279 -0
- package/skills/tinystruct-patterns/references/architecture.md +90 -0
- package/skills/tinystruct-patterns/references/data-handling.md +60 -0
- package/skills/tinystruct-patterns/references/database.md +99 -0
- package/skills/tinystruct-patterns/references/routing.md +64 -0
- package/skills/tinystruct-patterns/references/system-usage.md +97 -0
- package/skills/tinystruct-patterns/references/testing.md +72 -0
- package/skills/token-budget-advisor/SKILL.md +134 -0
- package/skills/ui-demo/SKILL.md +466 -0
- package/skills/ui-to-vue/SKILL.md +135 -0
- package/skills/uncloud/SKILL.md +344 -0
- package/skills/unified-memory/SKILL.md +170 -0
- package/skills/unified-notifications-ops/SKILL.md +188 -0
- package/skills/verification-loop/SKILL.md +129 -0
- package/skills/video-editing/SKILL.md +311 -0
- package/skills/videodb/SKILL.md +375 -0
- package/skills/visa-doc-translate/SKILL.md +117 -0
- package/skills/vite-patterns/SKILL.md +450 -0
- package/skills/vue-patterns/SKILL.md +471 -0
- package/skills/windows-desktop-e2e/SKILL.md +888 -0
- package/skills/workspace-surface-audit/SKILL.md +126 -0
- package/skills/x-api/SKILL.md +235 -0
package/lib/ai.js
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Codeply CLI — AI client
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Backends, selected by lib/config.js:
|
|
5
5
|
*
|
|
6
|
-
* codeply
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* codeply the 'ai-proxy' Supabase Edge Function the desktop app uses
|
|
7
|
+
* (see Codeply-App/supabase/functions/ai-proxy). No provider
|
|
8
|
+
* key lives in this CLI; it authenticates with the signed-in
|
|
9
|
+
* session from lib/auth.js, so usage counts against that
|
|
10
|
+
* account's shared daily cap.
|
|
10
11
|
*
|
|
11
|
-
* ollama
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* ollama Ollama's OpenAI-compatible /v1/chat/completions, either the
|
|
13
|
+
* cloud (ollama.com, bearer key) or a local daemon (no key).
|
|
14
|
+
* No Codeply sign-in required, and no daily cap.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
+
* openrouter, groq, openai BYOK: the user's own key against that
|
|
17
|
+
* provider's OpenAI-compatible chat completions endpoint.
|
|
18
|
+
* No Codeply account involved at all.
|
|
19
|
+
*
|
|
20
|
+
* anthropic BYOK against Anthropic's Messages API, which is NOT
|
|
21
|
+
* OpenAI-compatible (different endpoint, a top-level `system`
|
|
22
|
+
* field instead of a system message, and a `content` array in
|
|
23
|
+
* the response) — handled by its own converter below.
|
|
24
|
+
*
|
|
25
|
+
* All of these return the identical shape — {success, data:{choices:[{message}]}, …} —
|
|
16
26
|
* so nothing downstream (agent loop, edit engine) knows or cares which ran.
|
|
17
27
|
*/
|
|
18
28
|
const { SUPABASE_URL, SUPABASE_ANON_KEY, getAccessToken } = require('./auth');
|
|
19
|
-
const { getConfig } = require('./config');
|
|
29
|
+
const { getConfig, byokHint } = require('./config');
|
|
20
30
|
|
|
21
31
|
const AI_PROXY_URL = `${SUPABASE_URL}/functions/v1/ai-proxy`;
|
|
22
32
|
|
|
@@ -85,7 +95,10 @@ async function chatViaProxy(messages, opts) {
|
|
|
85
95
|
'Authorization': `Bearer ${token}`,
|
|
86
96
|
'apikey': SUPABASE_ANON_KEY,
|
|
87
97
|
},
|
|
88
|
-
|
|
98
|
+
// meta is optional, display-only context (what was asked, which file) —
|
|
99
|
+
// the proxy logs it to usage_history so CLI activity shows up in the
|
|
100
|
+
// admin dashboard next to the desktop app, same as this app's own calls.
|
|
101
|
+
body: JSON.stringify({ messages, opts, meta: opts.meta }),
|
|
89
102
|
});
|
|
90
103
|
} catch (e) {
|
|
91
104
|
// Network-level failure — also worth another go.
|
|
@@ -100,7 +113,11 @@ async function chatViaProxy(messages, opts) {
|
|
|
100
113
|
// A structured error from the function itself is final: it means the
|
|
101
114
|
// function ran and deliberately rejected us (auth, daily cap, provider
|
|
102
115
|
// error). Retrying that just burns the user's quota.
|
|
103
|
-
if (body.error)
|
|
116
|
+
if (body.error) {
|
|
117
|
+
const hitCap = isRateLimitError(body.error) || /daily .* (limit|cap)/i.test(body.error);
|
|
118
|
+
const error = hitCap ? `${body.error} ${byokHint()}` : body.error;
|
|
119
|
+
return { done: true, value: { success: false, error } };
|
|
120
|
+
}
|
|
104
121
|
|
|
105
122
|
return { retryable: isTransientStatus(res.status), error: describeStatus(res.status) };
|
|
106
123
|
});
|
|
@@ -156,6 +173,189 @@ async function chatViaOllama(messages, opts, cfg) {
|
|
|
156
173
|
});
|
|
157
174
|
}
|
|
158
175
|
|
|
176
|
+
// OpenAI-compatible /v1/chat/completions endpoints for the three providers
|
|
177
|
+
// that speak that same dialect. Ollama has its own function above because it
|
|
178
|
+
// also has to handle "no key at all for a local daemon", which these never do.
|
|
179
|
+
const OPENAI_COMPATIBLE = {
|
|
180
|
+
openrouter: {
|
|
181
|
+
url: 'https://openrouter.ai/api/v1/chat/completions',
|
|
182
|
+
label: 'OpenRouter',
|
|
183
|
+
extraHeaders: { 'HTTP-Referer': 'https://codeply.online', 'X-Title': 'Codeply' },
|
|
184
|
+
},
|
|
185
|
+
groq: {
|
|
186
|
+
url: 'https://api.groq.com/openai/v1/chat/completions',
|
|
187
|
+
label: 'Groq',
|
|
188
|
+
},
|
|
189
|
+
openai: {
|
|
190
|
+
url: 'https://api.openai.com/v1/chat/completions',
|
|
191
|
+
label: 'OpenAI',
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
async function chatViaOpenAICompatible(messages, opts, providerName, cfg) {
|
|
196
|
+
const { url, label, extraHeaders } = OPENAI_COMPATIBLE[providerName];
|
|
197
|
+
const { apiKey, model } = cfg[providerName];
|
|
198
|
+
if (!apiKey) {
|
|
199
|
+
return { success: false, error: `No ${label} API key set. Run \`codeply provider ${providerName} --key <key>\`.` };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return withRetries(async () => {
|
|
203
|
+
let res;
|
|
204
|
+
try {
|
|
205
|
+
res = await fetch(url, {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: {
|
|
208
|
+
'Content-Type': 'application/json',
|
|
209
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
210
|
+
...extraHeaders,
|
|
211
|
+
},
|
|
212
|
+
body: JSON.stringify({
|
|
213
|
+
model,
|
|
214
|
+
messages,
|
|
215
|
+
temperature: opts.temperature ?? 0,
|
|
216
|
+
...(opts.maxTokens ? { max_tokens: opts.maxTokens } : {}),
|
|
217
|
+
...(opts.json ? { response_format: { type: 'json_object' } } : {}),
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
} catch (e) {
|
|
221
|
+
return { retryable: true, error: e.message };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const body = await res.json().catch(() => ({}));
|
|
225
|
+
|
|
226
|
+
if (res.ok && body.choices?.[0]) {
|
|
227
|
+
return { done: true, value: { success: true, data: body, modelUsed: body.model || model } };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const apiError = body?.error?.message || body?.error;
|
|
231
|
+
if (apiError) {
|
|
232
|
+
const msg = typeof apiError === 'string' ? apiError : JSON.stringify(apiError);
|
|
233
|
+
const fatal = /not found|does not exist|unknown model|unauthor|invalid.*key|forbidden/i.test(msg);
|
|
234
|
+
if (fatal) return { done: true, value: { success: false, error: `${label}: ${msg}` } };
|
|
235
|
+
return { retryable: isTransientStatus(res.status), error: `${label}: ${msg}` };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return { retryable: isTransientStatus(res.status), error: describeStatus(res.status) };
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Below this many characters a block essentially never clears Anthropic's
|
|
243
|
+
// minimum-token floor for caching (1024 tokens on Sonnet-class models, higher
|
|
244
|
+
// on Haiku) — offering cache_control on it just adds a cache-write surcharge
|
|
245
|
+
// for a block that will never actually be served from cache. ~4 chars/token is
|
|
246
|
+
// a deliberately conservative floor, not a precise count.
|
|
247
|
+
const ANTHROPIC_CACHEABLE_MIN_CHARS = 4000;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Anthropic's Messages API isn't OpenAI-shaped: system prompt is a top-level
|
|
251
|
+
* field (not a message with role "system"), and the reply comes back as a
|
|
252
|
+
* `content` block array rather than `choices[0].message`. Converted both ways
|
|
253
|
+
* here so the rest of the codebase never has to know Anthropic is different.
|
|
254
|
+
*
|
|
255
|
+
* Prompt caching (`cache_control: {type:'ephemeral'}`) is applied at two
|
|
256
|
+
* breakpoints, because of how the agent loop in lib/agent.mjs actually calls
|
|
257
|
+
* this: one user turn can take up to 24 steps, and EVERY step resends the
|
|
258
|
+
* ENTIRE message array built up so far — there is no other way to talk to a
|
|
259
|
+
* stateless chat completions API. Without caching, a 20-step turn re-bills the
|
|
260
|
+
* system prompt and the whole growing transcript from scratch 20 times over.
|
|
261
|
+
* With it:
|
|
262
|
+
* 1. The system prompt (mode instructions + tool reference + skill index +
|
|
263
|
+
* project context) is byte-identical across every step of a turn, and
|
|
264
|
+
* usually across the whole session — cached at ~10% of its input-token
|
|
265
|
+
* cost after the first call.
|
|
266
|
+
* 2. Everything except the newest message is marked as a second breakpoint,
|
|
267
|
+
* so step N's request reuses step N-1's cache instead of re-billing the
|
|
268
|
+
* whole transcript-so-far — turning per-step cost from O(conversation
|
|
269
|
+
* length) into roughly O(what's new since the last step).
|
|
270
|
+
* Anthropic allows the read side of a cache hit to apply automatically for any
|
|
271
|
+
* request sharing a cached prefix, so these two breakpoints are enough; more
|
|
272
|
+
* would just spend the (4/hr) breakpoint limit for no further benefit here.
|
|
273
|
+
*/
|
|
274
|
+
async function chatViaAnthropic(messages, opts, cfg) {
|
|
275
|
+
const { apiKey, model } = cfg.anthropic;
|
|
276
|
+
if (!apiKey) {
|
|
277
|
+
return { success: false, error: 'No Anthropic API key set. Run `codeply provider anthropic --key <key>`.' };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const systemText = messages.filter((m) => m.role === 'system').map((m) => m.content).join('\n\n');
|
|
281
|
+
const turns = messages
|
|
282
|
+
.filter((m) => m.role !== 'system')
|
|
283
|
+
.map((m) => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content }));
|
|
284
|
+
|
|
285
|
+
// Anthropic has no json_object response mode — ask for it in plain English instead.
|
|
286
|
+
const systemText2 = opts.json
|
|
287
|
+
? `${systemText}\n\nRespond with ONLY a single valid JSON object. No prose, no markdown fences, nothing before or after it.`
|
|
288
|
+
: systemText;
|
|
289
|
+
|
|
290
|
+
const cacheControl = { type: 'ephemeral' };
|
|
291
|
+
const system = systemText2.length >= ANTHROPIC_CACHEABLE_MIN_CHARS
|
|
292
|
+
? [{ type: 'text', text: systemText2, cache_control: cacheControl }]
|
|
293
|
+
: systemText2;
|
|
294
|
+
|
|
295
|
+
// Breakpoint 2: everything but the newest message. A single-message request
|
|
296
|
+
// (the very first call of a brand-new turn) has nothing "so far" to cache.
|
|
297
|
+
if (turns.length >= 2) {
|
|
298
|
+
const idx = turns.length - 2;
|
|
299
|
+
if (turns[idx].content.length >= ANTHROPIC_CACHEABLE_MIN_CHARS) {
|
|
300
|
+
turns[idx] = {
|
|
301
|
+
role: turns[idx].role,
|
|
302
|
+
content: [{ type: 'text', text: turns[idx].content, cache_control: cacheControl }],
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return withRetries(async () => {
|
|
308
|
+
let res;
|
|
309
|
+
try {
|
|
310
|
+
res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
311
|
+
method: 'POST',
|
|
312
|
+
headers: {
|
|
313
|
+
'Content-Type': 'application/json',
|
|
314
|
+
'x-api-key': apiKey,
|
|
315
|
+
'anthropic-version': '2023-06-01',
|
|
316
|
+
},
|
|
317
|
+
body: JSON.stringify({
|
|
318
|
+
model,
|
|
319
|
+
system,
|
|
320
|
+
messages: turns,
|
|
321
|
+
max_tokens: opts.maxTokens || 8192,
|
|
322
|
+
temperature: opts.temperature ?? 0,
|
|
323
|
+
}),
|
|
324
|
+
});
|
|
325
|
+
} catch (e) {
|
|
326
|
+
return { retryable: true, error: e.message };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const body = await res.json().catch(() => ({}));
|
|
330
|
+
|
|
331
|
+
if (res.ok && Array.isArray(body.content)) {
|
|
332
|
+
const text = body.content.filter((b) => b.type === 'text').map((b) => b.text).join('');
|
|
333
|
+
const data = {
|
|
334
|
+
choices: [{ message: { role: 'assistant', content: text }, finish_reason: body.stop_reason === 'max_tokens' ? 'length' : 'stop' }],
|
|
335
|
+
usage: {
|
|
336
|
+
prompt_tokens: body.usage?.input_tokens || 0,
|
|
337
|
+
completion_tokens: body.usage?.output_tokens || 0,
|
|
338
|
+
total_tokens: (body.usage?.input_tokens || 0) + (body.usage?.output_tokens || 0),
|
|
339
|
+
// Surfaced for anyone instrumenting cost later — not read anywhere yet.
|
|
340
|
+
cache_read_tokens: body.usage?.cache_read_input_tokens || 0,
|
|
341
|
+
cache_write_tokens: body.usage?.cache_creation_input_tokens || 0,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
return { done: true, value: { success: true, data, modelUsed: model } };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const apiError = body?.error?.message || body?.error;
|
|
348
|
+
if (apiError) {
|
|
349
|
+
const msg = typeof apiError === 'string' ? apiError : JSON.stringify(apiError);
|
|
350
|
+
const fatal = /not_found_error|authentication_error|permission_error|invalid.*key/i.test(JSON.stringify(body.error || {})) || res.status === 401 || res.status === 403 || res.status === 404;
|
|
351
|
+
if (fatal) return { done: true, value: { success: false, error: `Anthropic: ${msg}` } };
|
|
352
|
+
return { retryable: isTransientStatus(res.status), error: `Anthropic: ${msg}` };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return { retryable: isTransientStatus(res.status), error: describeStatus(res.status) };
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
159
359
|
/**
|
|
160
360
|
* @param {Array<{role:string, content:string}>} messages
|
|
161
361
|
* @param {{ json?: boolean, maxTokens?: number, temperature?: number }} opts
|
|
@@ -163,9 +363,15 @@ async function chatViaOllama(messages, opts, cfg) {
|
|
|
163
363
|
*/
|
|
164
364
|
async function chat(messages, opts = {}) {
|
|
165
365
|
const cfg = getConfig();
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
:
|
|
366
|
+
switch (cfg.provider) {
|
|
367
|
+
case 'ollama': return chatViaOllama(messages, opts, cfg);
|
|
368
|
+
case 'anthropic': return chatViaAnthropic(messages, opts, cfg);
|
|
369
|
+
case 'openrouter':
|
|
370
|
+
case 'groq':
|
|
371
|
+
case 'openai':
|
|
372
|
+
return chatViaOpenAICompatible(messages, opts, cfg.provider, cfg);
|
|
373
|
+
default: return chatViaProxy(messages, opts);
|
|
374
|
+
}
|
|
169
375
|
}
|
|
170
376
|
|
|
171
377
|
/** Chat helper that expects a JSON object back and parses it. */
|
package/lib/apply-limit.js
CHANGED
|
@@ -26,13 +26,23 @@ async function checkApplyLimit() {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Records one successful file write toward the daily cap. Fire-and-forget.
|
|
31
|
+
* `linesAdded`/`linesRemoved` are what the admin dashboard's per-user view
|
|
32
|
+
* sums up to show how much someone actually changed, not just how often —
|
|
33
|
+
* deliberately just counts, never the file content itself.
|
|
34
|
+
*/
|
|
35
|
+
async function recordApplyEvent(filePath, linesAdded = 0, linesRemoved = 0) {
|
|
31
36
|
const session = await getSession();
|
|
32
37
|
if (!session) return;
|
|
33
38
|
const supabase = getClient();
|
|
34
39
|
try {
|
|
35
|
-
await supabase.from('apply_history').insert({
|
|
40
|
+
await supabase.from('apply_history').insert({
|
|
41
|
+
user_id: session.user.id,
|
|
42
|
+
file_path: filePath || '',
|
|
43
|
+
lines_added: linesAdded || 0,
|
|
44
|
+
lines_removed: linesRemoved || 0,
|
|
45
|
+
});
|
|
36
46
|
} catch (e) {
|
|
37
47
|
console.warn('[apply-limit] record failed:', e.message);
|
|
38
48
|
}
|
package/lib/config.js
CHANGED
|
@@ -2,14 +2,22 @@
|
|
|
2
2
|
* Codeply CLI — model provider configuration.
|
|
3
3
|
*
|
|
4
4
|
* Lives in ~/.codeply/config.json, next to auth.json, deliberately OUTSIDE the
|
|
5
|
-
* repo: it holds
|
|
5
|
+
* repo: it holds API keys and must never be committable. Environment
|
|
6
6
|
* variables win over the file so a key can be supplied per-shell without ever
|
|
7
7
|
* touching disk.
|
|
8
8
|
*
|
|
9
9
|
* Providers:
|
|
10
|
-
* codeply
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* codeply the hosted ai-proxy (default; needs `codeply login`), shares
|
|
11
|
+
* a daily cap with the desktop app
|
|
12
|
+
* ollama Ollama's OpenAI-compatible API — cloud (ollama.com, needs a
|
|
13
|
+
* key) or a local daemon (http://localhost:11434, no key)
|
|
14
|
+
* openrouter bring your own OpenRouter key, no cap
|
|
15
|
+
* groq bring your own Groq key, no cap
|
|
16
|
+
* anthropic bring your own Anthropic key, no cap
|
|
17
|
+
* openai bring your own OpenAI key, no cap
|
|
18
|
+
*
|
|
19
|
+
* The last four are "BYOK": once a key is set, the CLI talks to that
|
|
20
|
+
* provider directly and never touches the shared Codeply account limit.
|
|
13
21
|
*/
|
|
14
22
|
const fs = require('fs');
|
|
15
23
|
const path = require('path');
|
|
@@ -18,13 +26,16 @@ const os = require('os');
|
|
|
18
26
|
const configDir = path.join(os.homedir(), '.codeply');
|
|
19
27
|
const configPath = path.join(configDir, 'config.json');
|
|
20
28
|
|
|
29
|
+
const PROVIDERS = ['codeply', 'ollama', 'openrouter', 'groq', 'anthropic', 'openai'];
|
|
30
|
+
const BYOK_PROVIDERS = ['openrouter', 'groq', 'anthropic', 'openai'];
|
|
31
|
+
|
|
21
32
|
const DEFAULTS = {
|
|
22
33
|
provider: 'codeply',
|
|
23
|
-
ollama:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
},
|
|
34
|
+
ollama: { host: 'https://ollama.com', model: 'gemma4:31b', apiKey: '' },
|
|
35
|
+
openrouter: { model: 'openrouter/auto', apiKey: '' },
|
|
36
|
+
groq: { model: 'openai/gpt-oss-120b', apiKey: '' },
|
|
37
|
+
anthropic: { model: 'claude-sonnet-5', apiKey: '' },
|
|
38
|
+
openai: { model: 'gpt-4o-mini', apiKey: '' },
|
|
28
39
|
};
|
|
29
40
|
|
|
30
41
|
function readFile() {
|
|
@@ -41,6 +52,16 @@ function getConfig() {
|
|
|
41
52
|
const provider = process.env.CODEPLY_PROVIDER || file.provider || DEFAULTS.provider;
|
|
42
53
|
const fileOllama = file.ollama || {};
|
|
43
54
|
|
|
55
|
+
const byok = {};
|
|
56
|
+
for (const name of BYOK_PROVIDERS) {
|
|
57
|
+
const fileEntry = file[name] || {};
|
|
58
|
+
const envPrefix = name.toUpperCase();
|
|
59
|
+
byok[name] = {
|
|
60
|
+
model: process.env[`${envPrefix}_MODEL`] || fileEntry.model || DEFAULTS[name].model,
|
|
61
|
+
apiKey: process.env[`${envPrefix}_API_KEY`] || fileEntry.apiKey || '',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
44
65
|
return {
|
|
45
66
|
provider,
|
|
46
67
|
ollama: {
|
|
@@ -48,17 +69,19 @@ function getConfig() {
|
|
|
48
69
|
model: process.env.OLLAMA_MODEL || fileOllama.model || DEFAULTS.ollama.model,
|
|
49
70
|
apiKey: process.env.OLLAMA_API_KEY || fileOllama.apiKey || '',
|
|
50
71
|
},
|
|
72
|
+
...byok,
|
|
51
73
|
};
|
|
52
74
|
}
|
|
53
75
|
|
|
54
76
|
/** Merge a patch into the stored config. Written user-only where supported. */
|
|
55
77
|
function saveConfig(patch) {
|
|
56
78
|
const current = readFile();
|
|
57
|
-
const next = {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
const next = { ...current, ...patch };
|
|
80
|
+
for (const name of ['ollama', ...BYOK_PROVIDERS]) {
|
|
81
|
+
if (current[name] || patch[name]) {
|
|
82
|
+
next[name] = { ...(current[name] || {}), ...(patch[name] || {}) };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
62
85
|
try {
|
|
63
86
|
if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
|
|
64
87
|
fs.writeFileSync(configPath, JSON.stringify(next, null, 2), { mode: 0o600 });
|
|
@@ -77,13 +100,37 @@ function maskKey(key) {
|
|
|
77
100
|
return key.slice(0, 4) + '…' + key.slice(-4);
|
|
78
101
|
}
|
|
79
102
|
|
|
80
|
-
/** One-line description of where completions are coming from. */
|
|
103
|
+
/** One-line description of where completions are coming from, model included (for `codeply provider`). */
|
|
81
104
|
function describeProvider(cfg = getConfig()) {
|
|
82
105
|
if (cfg.provider === 'ollama') {
|
|
83
106
|
const where = /localhost|127\.0\.0\.1/.test(cfg.ollama.host) ? 'local' : 'cloud';
|
|
84
107
|
return `ollama ${where} · ${cfg.ollama.model}`;
|
|
85
108
|
}
|
|
109
|
+
if (BYOK_PROVIDERS.includes(cfg.provider)) {
|
|
110
|
+
return `${cfg.provider} (own key) · ${cfg[cfg.provider].model}`;
|
|
111
|
+
}
|
|
86
112
|
return 'codeply proxy';
|
|
87
113
|
}
|
|
88
114
|
|
|
89
|
-
|
|
115
|
+
/** Same, but without naming the specific model — for passive UI chrome (the TUI status line). */
|
|
116
|
+
function describeProviderShort(cfg = getConfig()) {
|
|
117
|
+
if (cfg.provider === 'ollama') {
|
|
118
|
+
const where = /localhost|127\.0\.0\.1/.test(cfg.ollama.host) ? 'local' : 'cloud';
|
|
119
|
+
return `ollama ${where}`;
|
|
120
|
+
}
|
|
121
|
+
if (BYOK_PROVIDERS.includes(cfg.provider)) {
|
|
122
|
+
return `${cfg.provider} (own key)`;
|
|
123
|
+
}
|
|
124
|
+
return 'codeply proxy';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Suggested next step when the shared codeply account hits its daily cap. */
|
|
128
|
+
function byokHint() {
|
|
129
|
+
return 'Have your own API key? Bring your own model instead, no shared daily cap: ' +
|
|
130
|
+
'codeply provider openrouter --key <key> (or groq, anthropic, openai).';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
getConfig, saveConfig, maskKey, describeProvider, describeProviderShort, byokHint,
|
|
135
|
+
configPath, PROVIDERS, BYOK_PROVIDERS,
|
|
136
|
+
};
|
package/lib/edit-engine.js
CHANGED
|
@@ -155,7 +155,7 @@ async function computeInstructionEdits(instruction, filePath) {
|
|
|
155
155
|
];
|
|
156
156
|
if (feedback) messages.push({ role: 'user', content: feedback });
|
|
157
157
|
|
|
158
|
-
const aiResult = await ai.chat(messages, { json: true });
|
|
158
|
+
const aiResult = await ai.chat(messages, { json: true, meta: { promptText: instruction, filePath } });
|
|
159
159
|
if (!aiResult.success) throw new Error(aiResult.error);
|
|
160
160
|
if (aiResult.modelUsed) lastModelUsed = aiResult.modelUsed;
|
|
161
161
|
const data = aiResult.data;
|