@neurocode-ai/core 1.18.8
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/neurocode +199 -0
- package/bunfig.toml +2 -0
- package/drizzle.config.ts +10 -0
- package/package.json +131 -0
- package/schema.json +2071 -0
- package/script/fix-node-pty.ts +28 -0
- package/script/migration.ts +196 -0
- package/src/account/sql.ts +39 -0
- package/src/account.ts +101 -0
- package/src/agent.ts +111 -0
- package/src/aisdk.ts +235 -0
- package/src/background-job.ts +365 -0
- package/src/catalog.ts +301 -0
- package/src/command.ts +64 -0
- package/src/config/agent.ts +25 -0
- package/src/config/attachments.ts +15 -0
- package/src/config/command.ts +12 -0
- package/src/config/compaction.ts +15 -0
- package/src/config/experimental.ts +18 -0
- package/src/config/formatter.ts +12 -0
- package/src/config/lsp.ts +18 -0
- package/src/config/markdown.ts +36 -0
- package/src/config/mcp.ts +48 -0
- package/src/config/plugin/agent.ts +179 -0
- package/src/config/plugin/command.ts +83 -0
- package/src/config/plugin/external.ts +91 -0
- package/src/config/plugin/provider.ts +113 -0
- package/src/config/plugin/reference.ts +70 -0
- package/src/config/plugin/skill.ts +50 -0
- package/src/config/plugin.ts +13 -0
- package/src/config/provider.ts +71 -0
- package/src/config/reference.ts +22 -0
- package/src/config/tool-output.ts +9 -0
- package/src/config/watcher.ts +7 -0
- package/src/config.ts +227 -0
- package/src/control-plane/move-session.ts +148 -0
- package/src/control-plane/workspace.sql.ts +20 -0
- package/src/credential/sql.ts +14 -0
- package/src/credential.ts +138 -0
- package/src/cross-spawn-spawner.ts +507 -0
- package/src/data-migration.sql.ts +6 -0
- package/src/database/database.ts +57 -0
- package/src/database/migration/20260127222353_familiar_lady_ursula.ts +107 -0
- package/src/database/migration/20260211171708_add_project_commands.ts +11 -0
- package/src/database/migration/20260213144116_wakeful_the_professor.ts +23 -0
- package/src/database/migration/20260225215848_workspace.ts +19 -0
- package/src/database/migration/20260227213759_add_session_workspace_id.ts +12 -0
- package/src/database/migration/20260228203230_blue_harpoon.ts +30 -0
- package/src/database/migration/20260303231226_add_workspace_fields.ts +15 -0
- package/src/database/migration/20260309230000_move_org_to_state.ts +15 -0
- package/src/database/migration/20260312043431_session_message_cursor.ts +16 -0
- package/src/database/migration/20260323234822_events.ts +26 -0
- package/src/database/migration/20260410174513_workspace-name.ts +29 -0
- package/src/database/migration/20260413175956_chief_energizer.ts +24 -0
- package/src/database/migration/20260423070820_add_icon_url_override.ts +14 -0
- package/src/database/migration/20260427172553_slow_nightmare.ts +30 -0
- package/src/database/migration/20260428004200_add_session_path.ts +11 -0
- package/src/database/migration/20260501142318_next_venus.ts +12 -0
- package/src/database/migration/20260504145000_add_sync_owner.ts +11 -0
- package/src/database/migration/20260507164347_add_workspace_time.ts +11 -0
- package/src/database/migration/20260510033149_session_usage.ts +56 -0
- package/src/database/migration/20260511000411_data_migration_state.ts +16 -0
- package/src/database/migration/20260511173437_session-metadata.ts +16 -0
- package/src/database/migration/20260601010001_normalize_storage_paths.ts +22 -0
- package/src/database/migration/20260601202201_amazing_prowler.ts +11 -0
- package/src/database/migration/20260602002951_lowly_union_jack.ts +24 -0
- package/src/database/migration/20260602182828_add_project_directories.ts +20 -0
- package/src/database/migration/20260603001617_session_message_projection_indexes.ts +19 -0
- package/src/database/migration/20260603040000_session_message_projection_order.ts +19 -0
- package/src/database/migration/20260603141458_session_input_inbox.ts +25 -0
- package/src/database/migration/20260603160727_jittery_ezekiel_stane.ts +20 -0
- package/src/database/migration/20260604172448_event_sourced_session_input.ts +47 -0
- package/src/database/migration/20260605003541_add_session_context_snapshot.ts +21 -0
- package/src/database/migration/20260605042240_add_context_epoch_agent.ts +11 -0
- package/src/database/migration/20260611035744_credential.ts +25 -0
- package/src/database/migration/20260611192811_lush_chimera.ts +25 -0
- package/src/database/migration/20260612174303_project_dir_strategy.ts +29 -0
- package/src/database/migration/20260622142730_simplify_session_context_epoch.ts +13 -0
- package/src/database/migration/20260622170816_reset_v2_session_state.ts +17 -0
- package/src/database/migration/20260622202450_simplify_session_input.ts +17 -0
- package/src/database/migration.gen.ts +44 -0
- package/src/database/migration.ts +81 -0
- package/src/database/path.ts +91 -0
- package/src/database/schema.gen.ts +274 -0
- package/src/database/schema.sql.ts +10 -0
- package/src/database/sqlite.bun.ts +183 -0
- package/src/database/sqlite.node.ts +178 -0
- package/src/database/sqlite.ts +8 -0
- package/src/effect/app-node-builder.ts +23 -0
- package/src/effect/app-node-platform.ts +18 -0
- package/src/effect/app-node.ts +14 -0
- package/src/effect/dfdf +1 -0
- package/src/effect/keyed-mutex.ts +45 -0
- package/src/effect/layer-node.ts +333 -0
- package/src/effect/memo-map.ts +3 -0
- package/src/effect/runtime.ts +21 -0
- package/src/effect/service-use.ts +43 -0
- package/src/event/sql.ts +25 -0
- package/src/event.ts +638 -0
- package/src/file-mutation.ts +207 -0
- package/src/file.ts +6 -0
- package/src/filesystem/fff.bun.ts +140 -0
- package/src/filesystem/fff.node.ts +138 -0
- package/src/filesystem/ignore.ts +67 -0
- package/src/filesystem/protected.ts +53 -0
- package/src/filesystem/search.ts +239 -0
- package/src/filesystem/watcher.ts +140 -0
- package/src/filesystem.ts +118 -0
- package/src/flag/flag.ts +78 -0
- package/src/fs-util.ts +274 -0
- package/src/git.ts +987 -0
- package/src/github-copilot/README.md +5 -0
- package/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
- package/src/github-copilot/chat/get-response-metadata.ts +15 -0
- package/src/github-copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
- package/src/github-copilot/chat/openai-compatible-api-types.ts +64 -0
- package/src/github-copilot/chat/openai-compatible-chat-language-model.ts +815 -0
- package/src/github-copilot/chat/openai-compatible-chat-options.ts +28 -0
- package/src/github-copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
- package/src/github-copilot/chat/openai-compatible-prepare-tools.ts +83 -0
- package/src/github-copilot/copilot-provider.ts +100 -0
- package/src/github-copilot/openai-compatible-error.ts +27 -0
- package/src/github-copilot/responses/convert-to-openai-responses-input.ts +335 -0
- package/src/github-copilot/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/github-copilot/responses/openai-config.ts +18 -0
- package/src/github-copilot/responses/openai-error.ts +22 -0
- package/src/github-copilot/responses/openai-responses-api-types.ts +214 -0
- package/src/github-copilot/responses/openai-responses-language-model.ts +1770 -0
- package/src/github-copilot/responses/openai-responses-prepare-tools.ts +173 -0
- package/src/github-copilot/responses/openai-responses-settings.ts +1 -0
- package/src/github-copilot/responses/tool/code-interpreter.ts +87 -0
- package/src/github-copilot/responses/tool/file-search.ts +127 -0
- package/src/github-copilot/responses/tool/image-generation.ts +114 -0
- package/src/github-copilot/responses/tool/local-shell.ts +64 -0
- package/src/github-copilot/responses/tool/web-search-preview.ts +103 -0
- package/src/github-copilot/responses/tool/web-search.ts +102 -0
- package/src/global.ts +87 -0
- package/src/id/id.ts +47 -0
- package/src/image/photon.ts +94 -0
- package/src/image.ts +81 -0
- package/src/installation/version.ts +8 -0
- package/src/instruction-context.ts +101 -0
- package/src/integration/connection.ts +12 -0
- package/src/integration.ts +520 -0
- package/src/location-mutation.ts +162 -0
- package/src/location-service-map.ts +18 -0
- package/src/location-services.ts +115 -0
- package/src/location.ts +39 -0
- package/src/markdown.d.ts +4 -0
- package/src/model.ts +41 -0
- package/src/models-dev.ts +260 -0
- package/src/npm-config.ts +40 -0
- package/src/npm.ts +269 -0
- package/src/oauth/page.ts +276 -0
- package/src/observability/logging.ts +71 -0
- package/src/observability/otlp.ts +79 -0
- package/src/observability/shared.ts +1 -0
- package/src/observability.ts +24 -0
- package/src/patch.ts +197 -0
- package/src/permission/saved.ts +79 -0
- package/src/permission/sql.ts +20 -0
- package/src/permission.ts +310 -0
- package/src/plugin/agent.ts +206 -0
- package/src/plugin/command/initialize.txt +65 -0
- package/src/plugin/command/review.txt +100 -0
- package/src/plugin/command.ts +25 -0
- package/src/plugin/host.ts +219 -0
- package/src/plugin/internal.ts +153 -0
- package/src/plugin/layer-map.example.ts +94 -0
- package/src/plugin/models-dev.ts +183 -0
- package/src/plugin/promise.ts +93 -0
- package/src/plugin/provider/alibaba.ts +15 -0
- package/src/plugin/provider/amazon-bedrock.ts +126 -0
- package/src/plugin/provider/anthropic.ts +27 -0
- package/src/plugin/provider/azure.ts +82 -0
- package/src/plugin/provider/cerebras.ts +26 -0
- package/src/plugin/provider/cloudflare-ai-gateway.ts +81 -0
- package/src/plugin/provider/cloudflare-workers-ai.ts +81 -0
- package/src/plugin/provider/cohere.ts +15 -0
- package/src/plugin/provider/deepinfra.ts +15 -0
- package/src/plugin/provider/dynamic.ts +31 -0
- package/src/plugin/provider/gateway.ts +15 -0
- package/src/plugin/provider/github-copilot.ts +52 -0
- package/src/plugin/provider/gitlab.ts +65 -0
- package/src/plugin/provider/google-vertex.ts +173 -0
- package/src/plugin/provider/google.ts +15 -0
- package/src/plugin/provider/groq.ts +15 -0
- package/src/plugin/provider/kilo.ts +21 -0
- package/src/plugin/provider/llmgateway.ts +26 -0
- package/src/plugin/provider/mistral.ts +15 -0
- package/src/plugin/provider/nvidia.ts +22 -0
- package/src/plugin/provider/openai-compatible.ts +17 -0
- package/src/plugin/provider/openai.ts +292 -0
- package/src/plugin/provider/opencode.ts +311 -0
- package/src/plugin/provider/openrouter.ts +36 -0
- package/src/plugin/provider/perplexity.ts +15 -0
- package/src/plugin/provider/sap-ai-core.ts +46 -0
- package/src/plugin/provider/snowflake-cortex.ts +89 -0
- package/src/plugin/provider/togetherai.ts +15 -0
- package/src/plugin/provider/venice.ts +15 -0
- package/src/plugin/provider/vercel.ts +27 -0
- package/src/plugin/provider/xai.ts +22 -0
- package/src/plugin/provider/zenmux.ts +21 -0
- package/src/plugin/provider.ts +71 -0
- package/src/plugin/skill/customize-neurocode.md +452 -0
- package/src/plugin/skill.ts +31 -0
- package/src/plugin/variant.ts +39 -0
- package/src/plugin.ts +167 -0
- package/src/policy.ts +49 -0
- package/src/process.ts +261 -0
- package/src/project/copy-strategies.ts +35 -0
- package/src/project/copy.ts +292 -0
- package/src/project/directories.ts +158 -0
- package/src/project/schema.ts +16 -0
- package/src/project/sql.ts +35 -0
- package/src/project.ts +136 -0
- package/src/provider.ts +25 -0
- package/src/pty/protocol.ts +37 -0
- package/src/pty/pty.bun.ts +26 -0
- package/src/pty/pty.node.ts +29 -0
- package/src/pty/pty.ts +25 -0
- package/src/pty/schema.ts +1 -0
- package/src/pty/ticket.ts +56 -0
- package/src/pty.ts +318 -0
- package/src/public-event-manifest.ts +7 -0
- package/src/question.ts +153 -0
- package/src/reference/guidance.ts +69 -0
- package/src/reference.ts +125 -0
- package/src/repository-cache.ts +259 -0
- package/src/repository.ts +214 -0
- package/src/ripgrep/binary.ts +132 -0
- package/src/ripgrep.ts +281 -0
- package/src/schema.ts +79 -0
- package/src/session/compaction.ts +241 -0
- package/src/session/context-epoch.ts +174 -0
- package/src/session/error.ts +24 -0
- package/src/session/event.ts +2 -0
- package/src/session/execution/local.ts +46 -0
- package/src/session/execution.ts +34 -0
- package/src/session/history.ts +101 -0
- package/src/session/info.ts +50 -0
- package/src/session/input.ts +288 -0
- package/src/session/message-updater.ts +397 -0
- package/src/session/message.ts +2 -0
- package/src/session/projector.ts +458 -0
- package/src/session/prompt.ts +1 -0
- package/src/session/revert.ts +121 -0
- package/src/session/run-coordinator.ts +104 -0
- package/src/session/runner/index.ts +28 -0
- package/src/session/runner/llm.ts +432 -0
- package/src/session/runner/max-steps.ts +16 -0
- package/src/session/runner/model.ts +218 -0
- package/src/session/runner/publish-llm-event.ts +423 -0
- package/src/session/runner/to-llm-message.ts +171 -0
- package/src/session/schema.ts +9 -0
- package/src/session/sql.ts +176 -0
- package/src/session/store.ts +63 -0
- package/src/session/todo.ts +78 -0
- package/src/session.ts +486 -0
- package/src/share/sql.ts +13 -0
- package/src/shell.ts +226 -0
- package/src/skill/discovery.ts +213 -0
- package/src/skill/guidance.ts +76 -0
- package/src/skill.ts +132 -0
- package/src/snapshot.ts +266 -0
- package/src/state.ts +128 -0
- package/src/system-context/builtins.ts +50 -0
- package/src/system-context/index.ts +320 -0
- package/src/system-context/registry.ts +49 -0
- package/src/tool/AGENTS.md +59 -0
- package/src/tool/application-tools.ts +57 -0
- package/src/tool/apply-patch.ts +219 -0
- package/src/tool/bash.ts +207 -0
- package/src/tool/builtins.ts +48 -0
- package/src/tool/edit.ts +223 -0
- package/src/tool/glob.ts +105 -0
- package/src/tool/grep.ts +137 -0
- package/src/tool/http-body.ts +30 -0
- package/src/tool/question.ts +94 -0
- package/src/tool/read-filesystem.ts +366 -0
- package/src/tool/read.ts +117 -0
- package/src/tool/registry.ts +147 -0
- package/src/tool/skill.ts +109 -0
- package/src/tool/todowrite.ts +62 -0
- package/src/tool/tool.ts +162 -0
- package/src/tool/tools.ts +13 -0
- package/src/tool/webfetch.ts +218 -0
- package/src/tool/websearch.ts +260 -0
- package/src/tool/write.ts +101 -0
- package/src/tool-output-store.ts +211 -0
- package/src/util/array.ts +10 -0
- package/src/util/binary.ts +41 -0
- package/src/util/effect-flock.ts +284 -0
- package/src/util/encode.ts +51 -0
- package/src/util/error.ts +70 -0
- package/src/util/flock.ts +358 -0
- package/src/util/glob.ts +34 -0
- package/src/util/hash.ts +11 -0
- package/src/util/identifier.ts +1 -0
- package/src/util/iife.ts +3 -0
- package/src/util/lazy.ts +11 -0
- package/src/util/module.ts +10 -0
- package/src/util/path.ts +37 -0
- package/src/util/retry.ts +42 -0
- package/src/util/slug.ts +74 -0
- package/src/util/token.ts +5 -0
- package/src/util/which.ts +14 -0
- package/src/util/wildcard.ts +14 -0
- package/src/v1/config/agent.ts +89 -0
- package/src/v1/config/attachment.ts +25 -0
- package/src/v1/config/command.ts +13 -0
- package/src/v1/config/config.ts +192 -0
- package/src/v1/config/console-state.ts +16 -0
- package/src/v1/config/error.ts +39 -0
- package/src/v1/config/formatter.ts +13 -0
- package/src/v1/config/layout.ts +6 -0
- package/src/v1/config/lsp.ts +80 -0
- package/src/v1/config/mcp.ts +63 -0
- package/src/v1/config/migrate.ts +254 -0
- package/src/v1/config/permission.ts +50 -0
- package/src/v1/config/plugin.ts +9 -0
- package/src/v1/config/provider-options.ts +227 -0
- package/src/v1/config/provider.ts +121 -0
- package/src/v1/config/server.ts +19 -0
- package/src/v1/config/skills.ts +13 -0
- package/src/v1/permission.ts +33 -0
- package/src/v1/session.ts +68 -0
- package/src/v2-schema.ts +3 -0
- package/src/workspace.ts +6 -0
- package/sst-env.d.ts +10 -0
- package/test/agent.test.ts +131 -0
- package/test/application-tools.test.ts +287 -0
- package/test/background-job.test.ts +106 -0
- package/test/catalog.test.ts +353 -0
- package/test/command.test.ts +56 -0
- package/test/config/agent.test.ts +349 -0
- package/test/config/command.test.ts +83 -0
- package/test/config/config.test.ts +798 -0
- package/test/config/fixtures/plugin/directory-plugin.ts +13 -0
- package/test/config/plugin.test.ts +248 -0
- package/test/config/provider-options.test.ts +224 -0
- package/test/config/provider.test.ts +269 -0
- package/test/config/skill.test.ts +80 -0
- package/test/credential.test.ts +36 -0
- package/test/database-migration.test.ts +644 -0
- package/test/effect/cross-spawn-spawner.test.ts +426 -0
- package/test/effect/keyed-mutex.test.ts +73 -0
- package/test/effect/layer-node/layer-node-types.test.ts +143 -0
- package/test/effect/layer-node/layer-node.test.ts +261 -0
- package/test/effect/layer-node/node-build.test.ts +125 -0
- package/test/effect/observability.test.ts +109 -0
- package/test/event.test.ts +1124 -0
- package/test/file-mutation.test.ts +368 -0
- package/test/filesystem/filesystem.test.ts +387 -0
- package/test/filesystem/ignore.test.ts +10 -0
- package/test/filesystem/search.test.ts +44 -0
- package/test/filesystem/watcher.test.ts +266 -0
- package/test/fixture/effect-flock-worker.ts +60 -0
- package/test/fixture/flock-worker.ts +72 -0
- package/test/fixture/git.ts +49 -0
- package/test/fixture/location.ts +26 -0
- package/test/fixture/tmpdir.ts +25 -0
- package/test/fixtures/recordings/session-runner/openai-chat-streams-text.json +27 -0
- package/test/git.test.ts +164 -0
- package/test/github-copilot/convert-to-copilot-messages.test.ts +523 -0
- package/test/github-copilot/copilot-chat-model.test.ts +592 -0
- package/test/github-copilot/openai-responses-language-model.test.ts +206 -0
- package/test/global.test.ts +16 -0
- package/test/instruction-context.test.ts +323 -0
- package/test/integration.test.ts +349 -0
- package/test/legacy-event-schema.test.ts +16 -0
- package/test/lib/effect.ts +53 -0
- package/test/lib/tool.ts +20 -0
- package/test/location-filesystem.test.ts +69 -0
- package/test/location-layer.test.ts +226 -0
- package/test/location-mutation.test.ts +178 -0
- package/test/location.test.ts +42 -0
- package/test/model.test.ts +23 -0
- package/test/models.test.ts +290 -0
- package/test/move-session.test.ts +235 -0
- package/test/npm-config.test.ts +51 -0
- package/test/npm.test.ts +84 -0
- package/test/oauth-page.test.ts +15 -0
- package/test/patch.test.ts +68 -0
- package/test/permission.test.ts +315 -0
- package/test/plugin/command.test.ts +47 -0
- package/test/plugin/fixture.ts +52 -0
- package/test/plugin/fixtures/config-effect-plugin.ts +15 -0
- package/test/plugin/fixtures/config-promise-plugin.ts +13 -0
- package/test/plugin/fixtures/invalid-plugin.ts +1 -0
- package/test/plugin/fixtures/models-dev.json +14 -0
- package/test/plugin/fixtures/provider-factory.ts +9 -0
- package/test/plugin/host.ts +303 -0
- package/test/plugin/models-dev.test.ts +170 -0
- package/test/plugin/promise.test.ts +67 -0
- package/test/plugin/provider-alibaba.test.ts +92 -0
- package/test/plugin/provider-amazon-bedrock.test.ts +622 -0
- package/test/plugin/provider-anthropic.test.ts +94 -0
- package/test/plugin/provider-azure-cognitive-services.test.ts +199 -0
- package/test/plugin/provider-azure.test.ts +293 -0
- package/test/plugin/provider-cerebras.test.ts +136 -0
- package/test/plugin/provider-cloudflare-ai-gateway.test.ts +426 -0
- package/test/plugin/provider-cloudflare-workers-ai.test.ts +279 -0
- package/test/plugin/provider-cohere.test.ts +123 -0
- package/test/plugin/provider-deepinfra.test.ts +159 -0
- package/test/plugin/provider-dynamic.test.ts +184 -0
- package/test/plugin/provider-gateway.test.ts +115 -0
- package/test/plugin/provider-github-copilot.test.ts +278 -0
- package/test/plugin/provider-gitlab.test.ts +308 -0
- package/test/plugin/provider-google-vertex-anthropic.test.ts +264 -0
- package/test/plugin/provider-google-vertex.test.ts +387 -0
- package/test/plugin/provider-google.test.ts +80 -0
- package/test/plugin/provider-groq.test.ts +121 -0
- package/test/plugin/provider-kilo.test.ts +99 -0
- package/test/plugin/provider-llmgateway.test.ts +80 -0
- package/test/plugin/provider-mistral.test.ts +129 -0
- package/test/plugin/provider-nvidia.test.ts +97 -0
- package/test/plugin/provider-openai-compatible.test.ts +107 -0
- package/test/plugin/provider-openai.test.ts +176 -0
- package/test/plugin/provider-opencode.test.ts +423 -0
- package/test/plugin/provider-openrouter.test.ts +113 -0
- package/test/plugin/provider-perplexity.test.ts +122 -0
- package/test/plugin/provider-sap-ai-core.test.ts +159 -0
- package/test/plugin/provider-snowflake-cortex.test.ts +261 -0
- package/test/plugin/provider-togetherai.test.ts +129 -0
- package/test/plugin/provider-venice.test.ts +115 -0
- package/test/plugin/provider-vercel.test.ts +83 -0
- package/test/plugin/provider-xai.test.ts +125 -0
- package/test/plugin/provider-zenmux.test.ts +110 -0
- package/test/plugin/skill.test.ts +25 -0
- package/test/plugin/variant.test.ts +67 -0
- package/test/plugin.test.ts +71 -0
- package/test/policy.test.ts +85 -0
- package/test/preload.ts +5 -0
- package/test/process/process.test.ts +368 -0
- package/test/project-copy.test.ts +400 -0
- package/test/project-directories.test.ts +61 -0
- package/test/project.test.ts +221 -0
- package/test/provider-mistral.test.ts +282 -0
- package/test/provider-xai-responses.test.ts +32 -0
- package/test/pty/info-schema.test.ts +32 -0
- package/test/pty/protocol.test.ts +27 -0
- package/test/pty/pty-session.test.ts +240 -0
- package/test/pty/ticket.test.ts +62 -0
- package/test/question.test.ts +114 -0
- package/test/reference-guidance.test.ts +76 -0
- package/test/reference.test.ts +78 -0
- package/test/repository-cache.test.ts +153 -0
- package/test/repository.test.ts +71 -0
- package/test/ripgrep.test.ts +65 -0
- package/test/session-compaction.test.ts +27 -0
- package/test/session-create.test.ts +425 -0
- package/test/session-history.test.ts +165 -0
- package/test/session-projector.test.ts +533 -0
- package/test/session-prompt.test.ts +584 -0
- package/test/session-run-coordinator.test.ts +418 -0
- package/test/session-runner-message.test.ts +501 -0
- package/test/session-runner-model.test.ts +347 -0
- package/test/session-runner-recorded.test.ts +193 -0
- package/test/session-runner-tool-events.test.ts +136 -0
- package/test/session-runner-tool-registry.test.ts +452 -0
- package/test/session-runner.test.ts +3365 -0
- package/test/session-todo.test.ts +94 -0
- package/test/session-tool-progress.test.ts +158 -0
- package/test/shared-schema.test.ts +206 -0
- package/test/shell.test.ts +108 -0
- package/test/skill/guidance.test.ts +144 -0
- package/test/skill-discovery.test.ts +165 -0
- package/test/skill.test.ts +125 -0
- package/test/snapshot.test.ts +178 -0
- package/test/state.test.ts +115 -0
- package/test/system-context/builtins.test.ts +129 -0
- package/test/system-context/index.test.ts +307 -0
- package/test/system-context/registry.test.ts +114 -0
- package/test/tool-apply-patch.test.ts +436 -0
- package/test/tool-bash.test.ts +437 -0
- package/test/tool-edit.test.ts +434 -0
- package/test/tool-output-store.test.ts +247 -0
- package/test/tool-question.test.ts +162 -0
- package/test/tool-read-filesystem.test.ts +118 -0
- package/test/tool-read.test.ts +674 -0
- package/test/tool-skill.test.ts +149 -0
- package/test/tool-todowrite.test.ts +125 -0
- package/test/tool-webfetch.test.ts +281 -0
- package/test/tool-websearch.test.ts +316 -0
- package/test/tool-write.test.ts +305 -0
- package/test/util/effect-flock.test.ts +392 -0
- package/test/util/flock.test.ts +428 -0
- package/test/util/which.test.ts +100 -0
- package/tsconfig.json +7 -0
package/src/tool/edit.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing V2 exact-edit leaf. Relative paths resolve within the active
|
|
3
|
+
* Location. Absolute paths inside that Location are accepted, while explicit
|
|
4
|
+
* absolute external paths retain mutation capability through a separate
|
|
5
|
+
* external_directory approval before edit approval.
|
|
6
|
+
*/
|
|
7
|
+
export * as EditTool from "./edit"
|
|
8
|
+
|
|
9
|
+
import { ToolFailure } from "@neurocode-ai/llm"
|
|
10
|
+
import { FileDiff } from "@neurocode-ai/schema/file-diff"
|
|
11
|
+
import { createTwoFilesPatch, diffLines } from "diff"
|
|
12
|
+
import { Effect, Layer, Schema } from "effect"
|
|
13
|
+
import { makeLocationNode } from "../effect/app-node"
|
|
14
|
+
import { FileMutation } from "../file-mutation"
|
|
15
|
+
import { FSUtil } from "../fs-util"
|
|
16
|
+
import { LocationMutation } from "../location-mutation"
|
|
17
|
+
import { PermissionV2 } from "../permission"
|
|
18
|
+
import { ToolRegistry } from "./registry"
|
|
19
|
+
import { Tool } from "./tool"
|
|
20
|
+
import { Tools } from "./tools"
|
|
21
|
+
|
|
22
|
+
export const name = "edit"
|
|
23
|
+
|
|
24
|
+
export const Input = Schema.Struct({
|
|
25
|
+
path: Schema.String.annotate({
|
|
26
|
+
description:
|
|
27
|
+
"File path to edit. Relative paths resolve within the active Location. Absolute paths inside that Location are accepted; external absolute paths require external_directory approval.",
|
|
28
|
+
}),
|
|
29
|
+
oldString: Schema.String.annotate({ description: "Exact text to replace" }),
|
|
30
|
+
newString: Schema.String.annotate({ description: "Replacement text, which must differ from oldString" }),
|
|
31
|
+
replaceAll: Schema.Boolean.pipe(Schema.optional).annotate({
|
|
32
|
+
description: "Replace all exact occurrences of oldString (default false)",
|
|
33
|
+
}),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export const Output = Schema.Struct({
|
|
37
|
+
files: Schema.Array(FileDiff.Info),
|
|
38
|
+
replacements: Schema.Number,
|
|
39
|
+
})
|
|
40
|
+
export type Output = typeof Output.Type
|
|
41
|
+
|
|
42
|
+
const normalizeLineEndings = (text: string) => text.replaceAll("\r\n", "\n")
|
|
43
|
+
const detectLineEnding = (text: string): "\n" | "\r\n" => (text.includes("\r\n") ? "\r\n" : "\n")
|
|
44
|
+
const convertToLineEnding = (text: string, ending: "\n" | "\r\n") =>
|
|
45
|
+
ending === "\n" ? normalizeLineEndings(text) : normalizeLineEndings(text).replaceAll("\n", "\r\n")
|
|
46
|
+
|
|
47
|
+
const splitBom = (text: string) =>
|
|
48
|
+
text.startsWith("\uFEFF") ? { bom: true, text: text.slice(1) } : { bom: false, text }
|
|
49
|
+
const joinBom = (text: string, bom: boolean) => (bom ? `\uFEFF${text}` : text)
|
|
50
|
+
const decodeUtf8 = (content: Uint8Array) => {
|
|
51
|
+
const bom = content[0] === 0xef && content[1] === 0xbb && content[2] === 0xbf
|
|
52
|
+
return { bom, content, text: new TextDecoder().decode(bom ? content.slice(3) : content) }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const countOccurrences = (content: string, search: string) => {
|
|
56
|
+
if (search === "") return content.length + 1
|
|
57
|
+
let count = 0
|
|
58
|
+
let offset = 0
|
|
59
|
+
while ((offset = content.indexOf(search, offset)) !== -1) {
|
|
60
|
+
count++
|
|
61
|
+
offset += search.length
|
|
62
|
+
}
|
|
63
|
+
return count
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const previewLines = (value: string, prefix: "+" | "-") => {
|
|
67
|
+
const lines = normalizeLineEndings(value).split("\n")
|
|
68
|
+
const shown = lines.slice(0, 6).map((line) => `${prefix}${line.length > 240 ? `${line.slice(0, 240)}...` : line}`)
|
|
69
|
+
if (lines.length > shown.length) shown.push(`${prefix}...`)
|
|
70
|
+
return shown
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const toModelOutput = (output: Output, oldString: string, newString: string) =>
|
|
74
|
+
[
|
|
75
|
+
`Edited file successfully: ${output.files[0]?.file}`,
|
|
76
|
+
`Replacements: ${output.replacements}`,
|
|
77
|
+
"```diff",
|
|
78
|
+
...previewLines(oldString, "-"),
|
|
79
|
+
...previewLines(newString, "+"),
|
|
80
|
+
"```",
|
|
81
|
+
].join("\n")
|
|
82
|
+
|
|
83
|
+
/** Deferred V2 edit behavior and UX integrations remain visible at the model-facing seam. */
|
|
84
|
+
// TODO: Port V1 fuzzy correction strategies only after exact-edit behavior is established: line-trimmed matching, block-anchor fallback, indentation correction, and similarity-threshold review.
|
|
85
|
+
// TODO: Add formatter integration after V2 formatter runtime exists.
|
|
86
|
+
// TODO: Publish watcher/file-edit events after V2 watcher integration exists.
|
|
87
|
+
// TODO: Add snapshots / undo after design exists.
|
|
88
|
+
// TODO: Add LSP notification and diagnostics after V2 LSP runtime exists.
|
|
89
|
+
|
|
90
|
+
const layer = Layer.effectDiscard(
|
|
91
|
+
Effect.gen(function* () {
|
|
92
|
+
const tools = yield* Tools.Service
|
|
93
|
+
const mutation = yield* LocationMutation.Service
|
|
94
|
+
const files = yield* FileMutation.Service
|
|
95
|
+
const fs = yield* FSUtil.Service
|
|
96
|
+
const permission = yield* PermissionV2.Service
|
|
97
|
+
|
|
98
|
+
yield* tools
|
|
99
|
+
.register({
|
|
100
|
+
[name]: Tool.withPermission(
|
|
101
|
+
Tool.make({
|
|
102
|
+
description:
|
|
103
|
+
"Replace exact text in one file. Relative paths resolve within the active Location. Absolute paths inside the Location are accepted. Explicit external absolute paths require external_directory approval before edit approval.",
|
|
104
|
+
input: Input,
|
|
105
|
+
output: Output,
|
|
106
|
+
toModelOutput: ({ input, output }) => [
|
|
107
|
+
{ type: "text", text: toModelOutput(output, input.oldString, input.newString) },
|
|
108
|
+
],
|
|
109
|
+
execute: (input, context) => {
|
|
110
|
+
const unableToEdit = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
111
|
+
effect.pipe(
|
|
112
|
+
Effect.mapError((error) =>
|
|
113
|
+
error instanceof FileMutation.StaleContentError
|
|
114
|
+
? new ToolFailure({
|
|
115
|
+
message: "File changed after permission approval. Read it again before editing.",
|
|
116
|
+
})
|
|
117
|
+
: new ToolFailure({ message: `Unable to edit ${input.path}` }),
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return Effect.gen(function* () {
|
|
122
|
+
const permissionSource = {
|
|
123
|
+
type: "tool" as const,
|
|
124
|
+
messageID: context.assistantMessageID,
|
|
125
|
+
callID: context.toolCallID,
|
|
126
|
+
}
|
|
127
|
+
if (input.oldString === input.newString) {
|
|
128
|
+
return yield* new ToolFailure({
|
|
129
|
+
message: "No changes to apply: oldString and newString are identical.",
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
if (input.oldString === "") {
|
|
133
|
+
return yield* new ToolFailure({
|
|
134
|
+
message: "oldString must not be empty. Use write to create or overwrite a file.",
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const target = yield* unableToEdit(mutation.resolve({ path: input.path, kind: "file" }))
|
|
139
|
+
const external = target.externalDirectory
|
|
140
|
+
if (external) {
|
|
141
|
+
yield* unableToEdit(
|
|
142
|
+
permission.assert({
|
|
143
|
+
...LocationMutation.externalDirectoryPermission(external),
|
|
144
|
+
sessionID: context.sessionID,
|
|
145
|
+
agent: context.agent,
|
|
146
|
+
source: permissionSource,
|
|
147
|
+
}),
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
yield* unableToEdit(
|
|
152
|
+
permission.assert({
|
|
153
|
+
action: "edit",
|
|
154
|
+
resources: [target.resource],
|
|
155
|
+
save: ["*"],
|
|
156
|
+
sessionID: context.sessionID,
|
|
157
|
+
agent: context.agent,
|
|
158
|
+
source: permissionSource,
|
|
159
|
+
}),
|
|
160
|
+
)
|
|
161
|
+
const source = decodeUtf8(yield* unableToEdit(fs.readFile(target.canonical)))
|
|
162
|
+
const ending = detectLineEnding(source.text)
|
|
163
|
+
const oldString = convertToLineEnding(input.oldString, ending)
|
|
164
|
+
const newString = convertToLineEnding(input.newString, ending)
|
|
165
|
+
const replacements = countOccurrences(source.text, oldString)
|
|
166
|
+
if (replacements === 0) {
|
|
167
|
+
return yield* new ToolFailure({
|
|
168
|
+
message:
|
|
169
|
+
"Could not find oldString in the file. It must match exactly, including whitespace and indentation.",
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
if (replacements > 1 && input.replaceAll !== true) {
|
|
173
|
+
return yield* new ToolFailure({
|
|
174
|
+
message:
|
|
175
|
+
"Found multiple exact matches for oldString. Provide more surrounding context or set replaceAll to true.",
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const replaced =
|
|
180
|
+
input.replaceAll === true
|
|
181
|
+
? source.text.replaceAll(oldString, newString)
|
|
182
|
+
: source.text.replace(oldString, newString)
|
|
183
|
+
const counts = diffLines(source.text, replaced).reduce(
|
|
184
|
+
(result, item) => ({
|
|
185
|
+
additions: result.additions + (item.added ? (item.count ?? 0) : 0),
|
|
186
|
+
deletions: result.deletions + (item.removed ? (item.count ?? 0) : 0),
|
|
187
|
+
}),
|
|
188
|
+
{ additions: 0, deletions: 0 },
|
|
189
|
+
)
|
|
190
|
+
const next = splitBom(replaced)
|
|
191
|
+
const result = yield* unableToEdit(
|
|
192
|
+
files.writeIfUnchanged({
|
|
193
|
+
target,
|
|
194
|
+
expected: source.content,
|
|
195
|
+
content: joinBom(next.text, source.bom || next.bom),
|
|
196
|
+
}),
|
|
197
|
+
)
|
|
198
|
+
return {
|
|
199
|
+
files: [
|
|
200
|
+
{
|
|
201
|
+
file: result.resource,
|
|
202
|
+
patch: createTwoFilesPatch(result.resource, result.resource, source.text, replaced),
|
|
203
|
+
status: "modified" as const,
|
|
204
|
+
...counts,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
replacements,
|
|
208
|
+
} satisfies Output
|
|
209
|
+
})
|
|
210
|
+
},
|
|
211
|
+
}),
|
|
212
|
+
"edit",
|
|
213
|
+
),
|
|
214
|
+
})
|
|
215
|
+
.pipe(Effect.orDie)
|
|
216
|
+
}),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
export const node = makeLocationNode({
|
|
220
|
+
name: "tool/edit",
|
|
221
|
+
layer,
|
|
222
|
+
deps: [ToolRegistry.node, LocationMutation.node, FileMutation.node, FSUtil.node, PermissionV2.node],
|
|
223
|
+
})
|
package/src/tool/glob.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export * as GlobTool from "./glob"
|
|
2
|
+
|
|
3
|
+
import { ToolFailure } from "@neurocode-ai/llm"
|
|
4
|
+
import { Effect, Layer, Schema } from "effect"
|
|
5
|
+
import path from "path"
|
|
6
|
+
import { makeLocationNode } from "../effect/app-node"
|
|
7
|
+
import { FileSystem } from "../filesystem"
|
|
8
|
+
import { Location } from "../location"
|
|
9
|
+
import { Ripgrep } from "../ripgrep"
|
|
10
|
+
import { RelativePath } from "../schema"
|
|
11
|
+
import { PermissionV2 } from "../permission"
|
|
12
|
+
import { ToolRegistry } from "./registry"
|
|
13
|
+
import { Tool } from "./tool"
|
|
14
|
+
import { Tools } from "./tools"
|
|
15
|
+
|
|
16
|
+
export const name = "glob"
|
|
17
|
+
|
|
18
|
+
export const Input = Schema.Struct({
|
|
19
|
+
pattern: FileSystem.GlobInput.fields.pattern.annotate({ description: "Glob pattern to match files against" }),
|
|
20
|
+
path: RelativePath.pipe(Schema.optional).annotate({
|
|
21
|
+
description: "Relative directory to search. Defaults to the active Location.",
|
|
22
|
+
}),
|
|
23
|
+
limit: FileSystem.GlobInput.fields.limit.annotate({
|
|
24
|
+
description: "Maximum results to return",
|
|
25
|
+
}),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const Output = Schema.Array(FileSystem.Entry)
|
|
29
|
+
type ModelOutput = typeof Output.Encoded
|
|
30
|
+
|
|
31
|
+
/** Format raw search results into the concise line-oriented output models expect. */
|
|
32
|
+
export const toModelOutput = (output: ModelOutput) => {
|
|
33
|
+
const lines = output.length === 0 ? ["No files found"] : output.map((item) => item.path)
|
|
34
|
+
return lines.join("\n")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Glob leaf that defaults its filesystem root to the active Location. */
|
|
38
|
+
const layer = Layer.effectDiscard(
|
|
39
|
+
Effect.gen(function* () {
|
|
40
|
+
const tools = yield* Tools.Service
|
|
41
|
+
const ripgrep = yield* Ripgrep.Service
|
|
42
|
+
const location = yield* Location.Service
|
|
43
|
+
const permission = yield* PermissionV2.Service
|
|
44
|
+
|
|
45
|
+
yield* tools
|
|
46
|
+
.register({
|
|
47
|
+
[name]: Tool.make({
|
|
48
|
+
description:
|
|
49
|
+
"Find files by glob pattern within the active Location. Returns concise relative file resources. Use a relative path to narrow the search and limit to bound the result count.",
|
|
50
|
+
input: Input,
|
|
51
|
+
output: Output,
|
|
52
|
+
toModelOutput: ({ output }) => [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: toModelOutput(
|
|
56
|
+
output.map((entry) => ({ ...entry, path: path.resolve(location.directory, entry.path) })),
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
execute: (input, context) =>
|
|
61
|
+
Effect.gen(function* () {
|
|
62
|
+
yield* permission.assert({
|
|
63
|
+
action: name,
|
|
64
|
+
resources: [input.pattern],
|
|
65
|
+
save: ["*"],
|
|
66
|
+
metadata: {
|
|
67
|
+
root: input.path ?? ".",
|
|
68
|
+
path: input.path,
|
|
69
|
+
limit: input.limit,
|
|
70
|
+
},
|
|
71
|
+
sessionID: context.sessionID,
|
|
72
|
+
agent: context.agent,
|
|
73
|
+
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
|
74
|
+
})
|
|
75
|
+
const cwd = path.resolve(location.directory, input.path ?? ".")
|
|
76
|
+
return yield* ripgrep
|
|
77
|
+
.glob({
|
|
78
|
+
cwd,
|
|
79
|
+
pattern: input.pattern,
|
|
80
|
+
limit: input.limit ?? Number.MAX_SAFE_INTEGER,
|
|
81
|
+
})
|
|
82
|
+
.pipe(
|
|
83
|
+
Effect.map((result) =>
|
|
84
|
+
result.map((entry) =>
|
|
85
|
+
FileSystem.Entry.make({
|
|
86
|
+
...entry,
|
|
87
|
+
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
|
|
88
|
+
}),
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
)
|
|
92
|
+
}).pipe(
|
|
93
|
+
Effect.mapError(() => new ToolFailure({ message: `Unable to find files matching ${input.pattern}` })),
|
|
94
|
+
),
|
|
95
|
+
}),
|
|
96
|
+
})
|
|
97
|
+
.pipe(Effect.orDie)
|
|
98
|
+
}),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
export const node = makeLocationNode({
|
|
102
|
+
name: "tool/glob",
|
|
103
|
+
layer,
|
|
104
|
+
deps: [ToolRegistry.node, Ripgrep.node, Location.node, PermissionV2.node],
|
|
105
|
+
})
|
package/src/tool/grep.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export * as GrepTool from "./grep"
|
|
2
|
+
|
|
3
|
+
import { ToolFailure } from "@neurocode-ai/llm"
|
|
4
|
+
import { Effect, Layer, Schema } from "effect"
|
|
5
|
+
import path from "path"
|
|
6
|
+
import { makeLocationNode } from "../effect/app-node"
|
|
7
|
+
import { FileSystem } from "../filesystem"
|
|
8
|
+
import { FSUtil } from "../fs-util"
|
|
9
|
+
import { Location } from "../location"
|
|
10
|
+
import { PermissionV2 } from "../permission"
|
|
11
|
+
import { Ripgrep } from "../ripgrep"
|
|
12
|
+
import { RelativePath } from "../schema"
|
|
13
|
+
import { ToolRegistry } from "./registry"
|
|
14
|
+
import { Tool } from "./tool"
|
|
15
|
+
import { Tools } from "./tools"
|
|
16
|
+
|
|
17
|
+
export const name = "grep"
|
|
18
|
+
|
|
19
|
+
export const Input = Schema.Struct({
|
|
20
|
+
pattern: FileSystem.GrepInput.fields.pattern.annotate({
|
|
21
|
+
description: "Regex pattern to search for in file contents",
|
|
22
|
+
}),
|
|
23
|
+
path: RelativePath.pipe(Schema.optional).annotate({
|
|
24
|
+
description: "Relative directory to search. Defaults to the active Location.",
|
|
25
|
+
}),
|
|
26
|
+
include: FileSystem.GrepInput.fields.include.annotate({
|
|
27
|
+
description: 'File glob to include in the search (for example, "*.js" or "*.{ts,tsx}")',
|
|
28
|
+
}),
|
|
29
|
+
limit: FileSystem.GrepInput.fields.limit.annotate({
|
|
30
|
+
description: "Maximum matches to return",
|
|
31
|
+
}),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export const Output = Schema.Array(FileSystem.Match)
|
|
35
|
+
type ModelOutput = typeof Output.Encoded
|
|
36
|
+
|
|
37
|
+
/** Format raw search matches into the familiar concise model output. */
|
|
38
|
+
export const toModelOutput = (output: ModelOutput) => {
|
|
39
|
+
const lines = output.length === 0 ? ["No files found"] : [`Found ${output.length} matches`]
|
|
40
|
+
let current = ""
|
|
41
|
+
for (const match of output) {
|
|
42
|
+
if (current !== match.entry.path) {
|
|
43
|
+
if (current) lines.push("")
|
|
44
|
+
current = match.entry.path
|
|
45
|
+
lines.push(`${match.entry.path}:`)
|
|
46
|
+
}
|
|
47
|
+
lines.push(` Line ${match.line}: ${match.text}`)
|
|
48
|
+
}
|
|
49
|
+
return lines.join("\n")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Grep leaf that defaults its filesystem root to the active Location. */
|
|
53
|
+
const layer = Layer.effectDiscard(
|
|
54
|
+
Effect.gen(function* () {
|
|
55
|
+
const tools = yield* Tools.Service
|
|
56
|
+
const fs = yield* FSUtil.Service
|
|
57
|
+
const ripgrep = yield* Ripgrep.Service
|
|
58
|
+
const location = yield* Location.Service
|
|
59
|
+
const permission = yield* PermissionV2.Service
|
|
60
|
+
|
|
61
|
+
yield* tools
|
|
62
|
+
.register({
|
|
63
|
+
[name]: Tool.make({
|
|
64
|
+
description:
|
|
65
|
+
"Search file contents by regular expression within the active Location or an absolute managed tool-output file. Use a path to narrow the search, include to filter files by glob, and limit to bound the match count. Returns concise file resources, line numbers, and bounded line previews.",
|
|
66
|
+
input: Input,
|
|
67
|
+
output: Output,
|
|
68
|
+
toModelOutput: ({ output }) => [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: toModelOutput(
|
|
72
|
+
output.map((match) => ({
|
|
73
|
+
...match,
|
|
74
|
+
entry: { ...match.entry, path: path.resolve(location.directory, match.entry.path) },
|
|
75
|
+
})),
|
|
76
|
+
),
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
execute: (input, context) =>
|
|
80
|
+
Effect.gen(function* () {
|
|
81
|
+
yield* permission.assert({
|
|
82
|
+
action: name,
|
|
83
|
+
resources: [input.pattern],
|
|
84
|
+
save: ["*"],
|
|
85
|
+
metadata: {
|
|
86
|
+
root: ".",
|
|
87
|
+
path: input.path,
|
|
88
|
+
include: input.include,
|
|
89
|
+
limit: input.limit,
|
|
90
|
+
},
|
|
91
|
+
sessionID: context.sessionID,
|
|
92
|
+
agent: context.agent,
|
|
93
|
+
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
|
94
|
+
})
|
|
95
|
+
const target = path.resolve(location.directory, input.path ?? ".")
|
|
96
|
+
const info = yield* fs.stat(target).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
|
97
|
+
return yield* ripgrep
|
|
98
|
+
.grep({
|
|
99
|
+
cwd: info?.type === "Directory" ? target : path.dirname(target),
|
|
100
|
+
pattern: input.pattern,
|
|
101
|
+
file: info?.type === "File" ? path.basename(target) : undefined,
|
|
102
|
+
include: input.include,
|
|
103
|
+
limit: input.limit ?? Number.MAX_SAFE_INTEGER,
|
|
104
|
+
})
|
|
105
|
+
.pipe(
|
|
106
|
+
Effect.map((result) =>
|
|
107
|
+
result.map((match) =>
|
|
108
|
+
FileSystem.Match.make({
|
|
109
|
+
...match,
|
|
110
|
+
entry: FileSystem.Entry.make({
|
|
111
|
+
...match.entry,
|
|
112
|
+
path: RelativePath.make(
|
|
113
|
+
path.relative(
|
|
114
|
+
location.directory,
|
|
115
|
+
path.resolve(
|
|
116
|
+
info?.type === "Directory" ? target : path.dirname(target),
|
|
117
|
+
match.entry.path,
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
),
|
|
121
|
+
}),
|
|
122
|
+
}),
|
|
123
|
+
),
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
}).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to grep for ${input.pattern}` }))),
|
|
127
|
+
}),
|
|
128
|
+
})
|
|
129
|
+
.pipe(Effect.orDie)
|
|
130
|
+
}),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
export const node = makeLocationNode({
|
|
134
|
+
name: "tool/grep",
|
|
135
|
+
layer,
|
|
136
|
+
deps: [ToolRegistry.node, FSUtil.node, Ripgrep.node, Location.node, PermissionV2.node],
|
|
137
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Effect, Stream } from "effect"
|
|
2
|
+
import { HttpClientResponse } from "effect/unstable/http"
|
|
3
|
+
|
|
4
|
+
export const collectBoundedResponseBody = (
|
|
5
|
+
response: HttpClientResponse.HttpClientResponse,
|
|
6
|
+
maximumBytes: number,
|
|
7
|
+
tooLarge: () => Error,
|
|
8
|
+
) =>
|
|
9
|
+
Effect.gen(function* () {
|
|
10
|
+
const contentLength = response.headers["content-length"]
|
|
11
|
+
const parsedSize = contentLength ? Number.parseInt(contentLength, 10) : undefined
|
|
12
|
+
const declaredSize =
|
|
13
|
+
parsedSize !== undefined && Number.isSafeInteger(parsedSize) && parsedSize >= 0 ? parsedSize : undefined
|
|
14
|
+
if (declaredSize !== undefined && declaredSize > maximumBytes) return yield* Effect.fail(tooLarge())
|
|
15
|
+
let body = Buffer.allocUnsafe(Math.min(maximumBytes, declaredSize || 64 * 1024))
|
|
16
|
+
let size = 0
|
|
17
|
+
yield* Stream.runForEach(response.stream, (chunk) => {
|
|
18
|
+
if (chunk.byteLength === 0) return Effect.void
|
|
19
|
+
if (size + chunk.byteLength > maximumBytes) return Effect.fail(tooLarge())
|
|
20
|
+
if (size + chunk.byteLength > body.byteLength) {
|
|
21
|
+
const grown = Buffer.allocUnsafe(Math.min(maximumBytes, Math.max(size + chunk.byteLength, body.byteLength * 2)))
|
|
22
|
+
body.copy(grown, 0, 0, size)
|
|
23
|
+
body = grown
|
|
24
|
+
}
|
|
25
|
+
body.set(chunk, size)
|
|
26
|
+
size += chunk.byteLength
|
|
27
|
+
return Effect.void
|
|
28
|
+
})
|
|
29
|
+
return body.subarray(0, size)
|
|
30
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export * as QuestionTool from "./question"
|
|
2
|
+
|
|
3
|
+
import { ToolFailure } from "@neurocode-ai/llm"
|
|
4
|
+
import { Effect, Layer, Schema } from "effect"
|
|
5
|
+
import { makeLocationNode } from "../effect/app-node"
|
|
6
|
+
import { PermissionV2 } from "../permission"
|
|
7
|
+
import { QuestionV2 } from "../question"
|
|
8
|
+
import { ToolRegistry } from "./registry"
|
|
9
|
+
import { Tool } from "./tool"
|
|
10
|
+
import { Tools } from "./tools"
|
|
11
|
+
|
|
12
|
+
export const name = "question"
|
|
13
|
+
|
|
14
|
+
export const description = `Use this tool when you need to ask the user questions during execution. This allows you to:
|
|
15
|
+
1. Gather user preferences or requirements
|
|
16
|
+
2. Clarify ambiguous instructions
|
|
17
|
+
3. Get decisions on implementation choices as you work
|
|
18
|
+
4. Offer choices to the user about what direction to take.
|
|
19
|
+
|
|
20
|
+
Usage notes:
|
|
21
|
+
- When \`custom\` is enabled (default), a "Type your own answer" option is added automatically; don't include "Other" or catch-all options
|
|
22
|
+
- Answers are returned as arrays of labels; set \`multiple: true\` to allow selecting more than one
|
|
23
|
+
- If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label`
|
|
24
|
+
|
|
25
|
+
export const Input = Schema.Struct({
|
|
26
|
+
questions: Schema.Array(QuestionV2.Prompt).annotate({ description: "Questions to ask" }),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const Output = Schema.Struct({
|
|
30
|
+
answers: Schema.Array(QuestionV2.Answer),
|
|
31
|
+
})
|
|
32
|
+
export type Output = typeof Output.Type
|
|
33
|
+
|
|
34
|
+
export const toModelOutput = (
|
|
35
|
+
questions: ReadonlyArray<QuestionV2.Prompt>,
|
|
36
|
+
answers: ReadonlyArray<QuestionV2.Answer>,
|
|
37
|
+
) => {
|
|
38
|
+
const formatted = questions
|
|
39
|
+
.map(
|
|
40
|
+
(question, index) =>
|
|
41
|
+
`"${question.question}"="${answers[index]?.length ? answers[index].join(", ") : "Unanswered"}"`,
|
|
42
|
+
)
|
|
43
|
+
.join(", ")
|
|
44
|
+
return `User has answered your questions: ${formatted}. You can now continue with the user's answers in mind.`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const layer = Layer.effectDiscard(
|
|
48
|
+
Effect.gen(function* () {
|
|
49
|
+
const tools = yield* Tools.Service
|
|
50
|
+
const question = yield* QuestionV2.Service
|
|
51
|
+
const permission = yield* PermissionV2.Service
|
|
52
|
+
|
|
53
|
+
yield* tools
|
|
54
|
+
.register({
|
|
55
|
+
[name]: Tool.make({
|
|
56
|
+
description,
|
|
57
|
+
input: Input,
|
|
58
|
+
output: Output,
|
|
59
|
+
toModelOutput: ({ input, output }) => [
|
|
60
|
+
{ type: "text", text: toModelOutput(input.questions, output.answers) },
|
|
61
|
+
],
|
|
62
|
+
execute: (input, context) =>
|
|
63
|
+
permission
|
|
64
|
+
.assert({
|
|
65
|
+
action: "question",
|
|
66
|
+
resources: ["*"],
|
|
67
|
+
sessionID: context.sessionID,
|
|
68
|
+
agent: context.agent,
|
|
69
|
+
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
|
70
|
+
})
|
|
71
|
+
.pipe(
|
|
72
|
+
Effect.mapError(() => new ToolFailure({ message: "Permission denied: question" })),
|
|
73
|
+
Effect.andThen(
|
|
74
|
+
question
|
|
75
|
+
.ask({
|
|
76
|
+
sessionID: context.sessionID,
|
|
77
|
+
questions: input.questions,
|
|
78
|
+
tool: { messageID: context.assistantMessageID, callID: context.toolCallID },
|
|
79
|
+
})
|
|
80
|
+
.pipe(Effect.orDie),
|
|
81
|
+
),
|
|
82
|
+
Effect.map((answers) => ({ answers })),
|
|
83
|
+
),
|
|
84
|
+
}),
|
|
85
|
+
})
|
|
86
|
+
.pipe(Effect.orDie)
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
export const node = makeLocationNode({
|
|
91
|
+
name: "tool/question",
|
|
92
|
+
layer,
|
|
93
|
+
deps: [ToolRegistry.node, PermissionV2.node, QuestionV2.node],
|
|
94
|
+
})
|