@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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export * as SkillTool from "./skill"
|
|
2
|
+
|
|
3
|
+
import path from "path"
|
|
4
|
+
import { ToolFailure } from "@neurocode-ai/llm"
|
|
5
|
+
import { Effect, Layer, Schema } from "effect"
|
|
6
|
+
import { makeLocationNode } from "../effect/app-node"
|
|
7
|
+
import { FSUtil } from "../fs-util"
|
|
8
|
+
import { SkillV2 } from "../skill"
|
|
9
|
+
import { PermissionV2 } from "../permission"
|
|
10
|
+
import { ToolRegistry } from "./registry"
|
|
11
|
+
import { Tool } from "./tool"
|
|
12
|
+
import { Tools } from "./tools"
|
|
13
|
+
|
|
14
|
+
export const name = "skill"
|
|
15
|
+
const FILE_LIMIT = 10
|
|
16
|
+
|
|
17
|
+
export const Input = Schema.Struct({
|
|
18
|
+
name: Schema.String.annotate({ description: "The name of the skill from the available skills list" }),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const Output = Schema.Struct({
|
|
22
|
+
name: Schema.String,
|
|
23
|
+
directory: Schema.String,
|
|
24
|
+
output: Schema.String,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export const description = [
|
|
28
|
+
"Load a specialized skill when the task at hand matches one of the available skills in the system context.",
|
|
29
|
+
"",
|
|
30
|
+
"Use this tool to inject the skill's instructions and resources into the current conversation. The output may contain detailed workflow guidance as well as references to scripts, files, etc. in the same directory as the skill.",
|
|
31
|
+
"",
|
|
32
|
+
"The skill name must match one of the available skills in the system context.",
|
|
33
|
+
].join("\n")
|
|
34
|
+
|
|
35
|
+
export const toModelOutput = (skill: SkillV2.Info, files: ReadonlyArray<string>) => {
|
|
36
|
+
const directory = path.dirname(skill.location)
|
|
37
|
+
return [
|
|
38
|
+
`<skill_content name="${skill.name}">`,
|
|
39
|
+
`# Skill: ${skill.name}`,
|
|
40
|
+
"",
|
|
41
|
+
skill.content.trim(),
|
|
42
|
+
"",
|
|
43
|
+
`Base directory for this skill: ${directory}`,
|
|
44
|
+
"Relative paths in this skill (e.g., scripts/, reference/) are relative to this base directory.",
|
|
45
|
+
"Note: file list is sampled.",
|
|
46
|
+
"",
|
|
47
|
+
"<skill_files>",
|
|
48
|
+
...files.map((file) => `<file>${file}</file>`),
|
|
49
|
+
"</skill_files>",
|
|
50
|
+
"</skill_content>",
|
|
51
|
+
].join("\n")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const unableToLoad = (name: string, error?: unknown) =>
|
|
55
|
+
new ToolFailure({ message: `Unable to load skill ${name}`, error })
|
|
56
|
+
|
|
57
|
+
const layer = Layer.effectDiscard(
|
|
58
|
+
Effect.gen(function* () {
|
|
59
|
+
const tools = yield* Tools.Service
|
|
60
|
+
const fs = yield* FSUtil.Service
|
|
61
|
+
const skills = yield* SkillV2.Service
|
|
62
|
+
const permission = yield* PermissionV2.Service
|
|
63
|
+
yield* tools
|
|
64
|
+
.register({
|
|
65
|
+
[name]: Tool.make({
|
|
66
|
+
description,
|
|
67
|
+
input: Input,
|
|
68
|
+
output: Output,
|
|
69
|
+
toModelOutput: ({ output }) => [{ type: "text", text: output.output }],
|
|
70
|
+
execute: (input, context) =>
|
|
71
|
+
Effect.gen(function* () {
|
|
72
|
+
const current = yield* skills.list()
|
|
73
|
+
const skill = current.find((skill) => skill.name === input.name)
|
|
74
|
+
if (!skill) return yield* unableToLoad(input.name)
|
|
75
|
+
return yield* Effect.gen(function* () {
|
|
76
|
+
yield* permission.assert({
|
|
77
|
+
action: name,
|
|
78
|
+
resources: [skill.name],
|
|
79
|
+
save: [skill.name],
|
|
80
|
+
sessionID: context.sessionID,
|
|
81
|
+
agent: context.agent,
|
|
82
|
+
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
|
83
|
+
})
|
|
84
|
+
const directory = path.dirname(skill.location)
|
|
85
|
+
const files =
|
|
86
|
+
path.basename(skill.location) === "SKILL.md"
|
|
87
|
+
? (yield* fs.glob("**/*", { cwd: directory, absolute: true, include: "file", dot: true }))
|
|
88
|
+
.filter((file) => path.basename(file) !== "SKILL.md")
|
|
89
|
+
.toSorted()
|
|
90
|
+
.slice(0, FILE_LIMIT)
|
|
91
|
+
: []
|
|
92
|
+
return {
|
|
93
|
+
name: skill.name,
|
|
94
|
+
directory,
|
|
95
|
+
output: toModelOutput(skill, files),
|
|
96
|
+
}
|
|
97
|
+
}).pipe(Effect.mapError((error) => unableToLoad(input.name, error)))
|
|
98
|
+
}),
|
|
99
|
+
}),
|
|
100
|
+
})
|
|
101
|
+
.pipe(Effect.orDie)
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
export const node = makeLocationNode({
|
|
106
|
+
name: "tool/skill",
|
|
107
|
+
layer,
|
|
108
|
+
deps: [ToolRegistry.node, FSUtil.node, SkillV2.node, PermissionV2.node],
|
|
109
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export * as TodoWriteTool from "./todowrite"
|
|
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 { SessionTodo } from "../session/todo"
|
|
8
|
+
import { ToolRegistry } from "./registry"
|
|
9
|
+
import { Tool } from "./tool"
|
|
10
|
+
import { Tools } from "./tools"
|
|
11
|
+
|
|
12
|
+
export const name = "todowrite"
|
|
13
|
+
|
|
14
|
+
export const Input = Schema.Struct({
|
|
15
|
+
todos: Schema.Array(SessionTodo.Info).annotate({ description: "The updated todo list" }),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const Output = Schema.Struct({
|
|
19
|
+
todos: Schema.Array(SessionTodo.Info),
|
|
20
|
+
})
|
|
21
|
+
export type Output = typeof Output.Type
|
|
22
|
+
|
|
23
|
+
export const toModelOutput = (output: Output) => JSON.stringify(output.todos, null, 2)
|
|
24
|
+
|
|
25
|
+
const layer = Layer.effectDiscard(
|
|
26
|
+
Effect.gen(function* () {
|
|
27
|
+
const tools = yield* Tools.Service
|
|
28
|
+
const todos = yield* SessionTodo.Service
|
|
29
|
+
const permission = yield* PermissionV2.Service
|
|
30
|
+
|
|
31
|
+
yield* tools
|
|
32
|
+
.register({
|
|
33
|
+
[name]: Tool.make({
|
|
34
|
+
description:
|
|
35
|
+
"Create and maintain a structured task list for the current coding session. Use it to track progress during multi-step work and keep todo statuses current.",
|
|
36
|
+
input: Input,
|
|
37
|
+
output: Output,
|
|
38
|
+
toModelOutput: ({ output }) => [{ type: "text", text: toModelOutput(output) }],
|
|
39
|
+
execute: (input, context) =>
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
yield* permission.assert({
|
|
42
|
+
action: name,
|
|
43
|
+
resources: ["*"],
|
|
44
|
+
save: ["*"],
|
|
45
|
+
sessionID: context.sessionID,
|
|
46
|
+
agent: context.agent,
|
|
47
|
+
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
|
48
|
+
})
|
|
49
|
+
yield* todos.update({ sessionID: context.sessionID, todos: input.todos })
|
|
50
|
+
return { todos: input.todos }
|
|
51
|
+
}).pipe(Effect.mapError(() => new ToolFailure({ message: "Unable to update todos" }))),
|
|
52
|
+
}),
|
|
53
|
+
})
|
|
54
|
+
.pipe(Effect.orDie)
|
|
55
|
+
}),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
export const node = makeLocationNode({
|
|
59
|
+
name: "tool/todowrite",
|
|
60
|
+
layer,
|
|
61
|
+
deps: [ToolRegistry.node, PermissionV2.node, SessionTodo.node],
|
|
62
|
+
})
|
package/src/tool/tool.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
export * as Tool from "./tool"
|
|
2
|
+
|
|
3
|
+
import { ToolDefinition, ToolFailure, ToolOutput, type ToolCall } from "@neurocode-ai/llm"
|
|
4
|
+
import { Effect, JsonSchema, Schema } from "effect"
|
|
5
|
+
import type { AgentV2 } from "../agent"
|
|
6
|
+
import type { SessionMessage } from "../session/message"
|
|
7
|
+
import type { SessionSchema } from "../session/schema"
|
|
8
|
+
|
|
9
|
+
export interface Context {
|
|
10
|
+
readonly sessionID: SessionSchema.ID
|
|
11
|
+
readonly agent: AgentV2.ID
|
|
12
|
+
readonly assistantMessageID: SessionMessage.ID
|
|
13
|
+
readonly toolCallID: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SchemaType<A> = Schema.Codec<A, any, never, never>
|
|
17
|
+
|
|
18
|
+
declare const TypeId: unique symbol
|
|
19
|
+
|
|
20
|
+
export interface Definition<Input extends SchemaType<any>, Output extends SchemaType<any>> {
|
|
21
|
+
readonly [TypeId]: {
|
|
22
|
+
readonly _Input: Input
|
|
23
|
+
readonly _Output: Output
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type AnyTool = Definition<any, any>
|
|
28
|
+
export const Failure = ToolFailure
|
|
29
|
+
export type Failure = ToolFailure
|
|
30
|
+
|
|
31
|
+
export class RegistrationError extends Schema.TaggedErrorClass<RegistrationError>()("Tool.RegistrationError", {
|
|
32
|
+
name: Schema.String,
|
|
33
|
+
message: Schema.String,
|
|
34
|
+
}) {}
|
|
35
|
+
|
|
36
|
+
export type Content =
|
|
37
|
+
| { readonly type: "text"; readonly text: string }
|
|
38
|
+
| { readonly type: "file"; readonly data: string; readonly mime: string; readonly name?: string }
|
|
39
|
+
|
|
40
|
+
type Config<
|
|
41
|
+
Input extends SchemaType<any>,
|
|
42
|
+
Output extends SchemaType<any>,
|
|
43
|
+
Structured extends SchemaType<any> = Output,
|
|
44
|
+
> = {
|
|
45
|
+
readonly description: string
|
|
46
|
+
readonly input: Input
|
|
47
|
+
readonly output: Output
|
|
48
|
+
readonly structured?: Structured
|
|
49
|
+
readonly toStructuredOutput?: (input: {
|
|
50
|
+
readonly input: Schema.Schema.Type<Input>
|
|
51
|
+
readonly output: Output["Encoded"]
|
|
52
|
+
}) => Schema.Schema.Type<Structured>
|
|
53
|
+
readonly execute: (
|
|
54
|
+
input: Schema.Schema.Type<Input>,
|
|
55
|
+
context: Context,
|
|
56
|
+
) => Effect.Effect<Schema.Schema.Type<Output>, ToolFailure>
|
|
57
|
+
readonly toModelOutput?: (input: {
|
|
58
|
+
readonly input: Schema.Schema.Type<Input>
|
|
59
|
+
readonly output: Output["Encoded"]
|
|
60
|
+
}) => ReadonlyArray<Content>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type Runtime = {
|
|
64
|
+
readonly permission?: string
|
|
65
|
+
readonly definition: (name: string) => ToolDefinition
|
|
66
|
+
readonly settle: (call: ToolCall, context: Context) => Effect.Effect<ToolOutput, ToolFailure>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const runtimes = new WeakMap<AnyTool, Runtime>()
|
|
70
|
+
|
|
71
|
+
export function make<
|
|
72
|
+
Input extends SchemaType<any>,
|
|
73
|
+
Output extends SchemaType<any>,
|
|
74
|
+
Structured extends SchemaType<any> = Output,
|
|
75
|
+
>(config: Config<Input, Output, Structured>): Definition<Input, Structured> {
|
|
76
|
+
const tool = Object.freeze({}) as Definition<Input, Structured>
|
|
77
|
+
const definitions = new Map<string, ToolDefinition>()
|
|
78
|
+
runtimes.set(tool, {
|
|
79
|
+
definition: (name) => {
|
|
80
|
+
const cached = definitions.get(name)
|
|
81
|
+
if (cached) return cached
|
|
82
|
+
const definition = new ToolDefinition({
|
|
83
|
+
name,
|
|
84
|
+
description: config.description,
|
|
85
|
+
inputSchema: toJsonSchema(config.input),
|
|
86
|
+
outputSchema: toJsonSchema(config.structured ?? config.output),
|
|
87
|
+
})
|
|
88
|
+
definitions.set(name, definition)
|
|
89
|
+
return definition
|
|
90
|
+
},
|
|
91
|
+
settle: (call, context) =>
|
|
92
|
+
Schema.decodeUnknownEffect(config.input)(call.input).pipe(
|
|
93
|
+
Effect.mapError((error) => new ToolFailure({ message: `Invalid tool input: ${error.message}` })),
|
|
94
|
+
Effect.flatMap((input) =>
|
|
95
|
+
config.execute(input, context).pipe(
|
|
96
|
+
Effect.flatMap((output) =>
|
|
97
|
+
Schema.encodeEffect(config.output)(output).pipe(
|
|
98
|
+
Effect.flatMap((output) => {
|
|
99
|
+
if (!config.structured || !config.toStructuredOutput)
|
|
100
|
+
return Effect.succeed({ output, structured: output })
|
|
101
|
+
return Schema.encodeEffect(config.structured)(config.toStructuredOutput({ input, output })).pipe(
|
|
102
|
+
Effect.map((structured) => ({ output, structured })),
|
|
103
|
+
)
|
|
104
|
+
}),
|
|
105
|
+
Effect.mapError(
|
|
106
|
+
(error) =>
|
|
107
|
+
new ToolFailure({
|
|
108
|
+
message: `Tool returned an invalid value for its output schema: ${error.message}`,
|
|
109
|
+
}),
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
Effect.map(({ output, structured }) => ({
|
|
114
|
+
structured,
|
|
115
|
+
content:
|
|
116
|
+
config.toModelOutput?.({ input, output }).map((part) =>
|
|
117
|
+
part.type === "text"
|
|
118
|
+
? { type: "text" as const, text: part.text }
|
|
119
|
+
: {
|
|
120
|
+
type: "file" as const,
|
|
121
|
+
uri: `data:${part.mime};base64,${part.data}`,
|
|
122
|
+
mime: part.mime,
|
|
123
|
+
name: part.name,
|
|
124
|
+
},
|
|
125
|
+
) ?? (typeof output === "string" ? [{ type: "text" as const, text: output }] : []),
|
|
126
|
+
})),
|
|
127
|
+
),
|
|
128
|
+
),
|
|
129
|
+
),
|
|
130
|
+
})
|
|
131
|
+
return tool
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const validateName = (name: string) =>
|
|
135
|
+
/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(name)
|
|
136
|
+
? Effect.void
|
|
137
|
+
: Effect.fail(new RegistrationError({ name, message: `Invalid tool name: ${name}` }))
|
|
138
|
+
|
|
139
|
+
export const withPermission = <Input extends SchemaType<any>, Output extends SchemaType<any>>(
|
|
140
|
+
tool: Definition<Input, Output>,
|
|
141
|
+
permission: string,
|
|
142
|
+
) => {
|
|
143
|
+
const decorated = Object.freeze({}) as Definition<Input, Output>
|
|
144
|
+
runtimes.set(decorated, { ...runtimeOf(tool), permission })
|
|
145
|
+
return decorated
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const permission = (tool: AnyTool, name: string) => runtimeOf(tool).permission ?? name
|
|
149
|
+
export const definition = (name: string, tool: AnyTool) => runtimeOf(tool).definition(name)
|
|
150
|
+
export const settle = (tool: AnyTool, call: ToolCall, context: Context) => runtimeOf(tool).settle(call, context)
|
|
151
|
+
|
|
152
|
+
function runtimeOf(tool: AnyTool) {
|
|
153
|
+
const runtime = runtimes.get(tool)
|
|
154
|
+
if (!runtime) throw new TypeError("Invalid Core Tool value")
|
|
155
|
+
return runtime
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function toJsonSchema(schema: Schema.Top): JsonSchema.JsonSchema {
|
|
159
|
+
const document = Schema.toJsonSchemaDocument(schema)
|
|
160
|
+
if (Object.keys(document.definitions).length === 0) return document.schema
|
|
161
|
+
return { ...document.schema, $defs: document.definitions }
|
|
162
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * as Tools from "./tools"
|
|
2
|
+
|
|
3
|
+
import { Context, Effect, Scope } from "effect"
|
|
4
|
+
import { Tool } from "./tool"
|
|
5
|
+
|
|
6
|
+
export interface Interface {
|
|
7
|
+
readonly register: (
|
|
8
|
+
tools: Readonly<Record<string, Tool.AnyTool>>,
|
|
9
|
+
) => Effect.Effect<void, Tool.RegistrationError, Scope.Scope>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Narrow registration-only Location capability. */
|
|
13
|
+
export class Service extends Context.Service<Service, Interface>()("@neurocode/v2/Tools") {}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
export * as WebFetchTool from "./webfetch"
|
|
2
|
+
|
|
3
|
+
import { ToolFailure } from "@neurocode-ai/llm"
|
|
4
|
+
import { Duration, Effect, Layer, Schema } from "effect"
|
|
5
|
+
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
|
6
|
+
import { Parser } from "htmlparser2"
|
|
7
|
+
import TurndownService from "turndown"
|
|
8
|
+
import { makeLocationNode } from "../effect/app-node"
|
|
9
|
+
import { LayerNodePlatform } from "../effect/app-node-platform"
|
|
10
|
+
import { PermissionV2 } from "../permission"
|
|
11
|
+
import { collectBoundedResponseBody } from "./http-body"
|
|
12
|
+
import { ToolRegistry } from "./registry"
|
|
13
|
+
import { Tool } from "./tool"
|
|
14
|
+
import { Tools } from "./tools"
|
|
15
|
+
|
|
16
|
+
export const name = "webfetch"
|
|
17
|
+
export const MAX_RESPONSE_BYTES = 5 * 1024 * 1024
|
|
18
|
+
export const DEFAULT_TIMEOUT_SECONDS = 30
|
|
19
|
+
export const MAX_TIMEOUT_SECONDS = 120
|
|
20
|
+
|
|
21
|
+
export const description = `Fetch content from an HTTP or HTTPS URL and return it as text, markdown, or HTML. Markdown is the default.
|
|
22
|
+
|
|
23
|
+
Use a more targeted tool when one is available. This tool is read-only. Large text results may be replaced with a preview while the complete output is retained in managed storage.`
|
|
24
|
+
|
|
25
|
+
const Timeout = Schema.Number.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(MAX_TIMEOUT_SECONDS))
|
|
26
|
+
|
|
27
|
+
export const Input = Schema.Struct({
|
|
28
|
+
url: Schema.String.annotate({ description: "The HTTP or HTTPS URL to fetch content from" }),
|
|
29
|
+
format: Schema.Literals(["text", "markdown", "html"])
|
|
30
|
+
.annotate({ description: "The format to return the content in. Defaults to markdown." })
|
|
31
|
+
.pipe(Schema.withDecodingDefault(Effect.succeed("markdown" as const))),
|
|
32
|
+
timeout: Timeout.pipe(Schema.optional).annotate({
|
|
33
|
+
description: `Optional timeout in seconds (maximum: ${MAX_TIMEOUT_SECONDS})`,
|
|
34
|
+
}),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const Output = Schema.Struct({
|
|
38
|
+
url: Schema.String,
|
|
39
|
+
contentType: Schema.String,
|
|
40
|
+
format: Input.fields.format,
|
|
41
|
+
output: Schema.String,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
type Format = (typeof Input.Type)["format"]
|
|
45
|
+
|
|
46
|
+
const acceptHeader = (format: Format) => {
|
|
47
|
+
switch (format) {
|
|
48
|
+
case "markdown":
|
|
49
|
+
return "text/markdown;q=1.0, text/x-markdown;q=0.9, text/plain;q=0.8, text/html;q=0.7, */*;q=0.1"
|
|
50
|
+
case "text":
|
|
51
|
+
return "text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1"
|
|
52
|
+
case "html":
|
|
53
|
+
return "text/html;q=1.0, application/xhtml+xml;q=0.9, text/plain;q=0.8, text/markdown;q=0.7, */*;q=0.1"
|
|
54
|
+
}
|
|
55
|
+
return "*/*"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const headers = (format: Format, userAgent: string) => ({
|
|
59
|
+
"User-Agent": userAgent,
|
|
60
|
+
Accept: acceptHeader(format),
|
|
61
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const browserUserAgent =
|
|
65
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
|
|
66
|
+
|
|
67
|
+
const isCloudflareChallenge = (error: unknown) => {
|
|
68
|
+
if (!error || typeof error !== "object" || !("reason" in error)) return false
|
|
69
|
+
const reason = error.reason
|
|
70
|
+
if (
|
|
71
|
+
!reason ||
|
|
72
|
+
typeof reason !== "object" ||
|
|
73
|
+
!("_tag" in reason) ||
|
|
74
|
+
reason._tag !== "StatusCodeError" ||
|
|
75
|
+
!("response" in reason)
|
|
76
|
+
)
|
|
77
|
+
return false
|
|
78
|
+
const response = reason.response as HttpClientResponse.HttpClientResponse
|
|
79
|
+
return response.status === 403 && response.headers["cf-mitigated"] === "challenge"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const request = (url: string, format: Format, userAgent = browserUserAgent) =>
|
|
83
|
+
HttpClientRequest.get(url).pipe(HttpClientRequest.setHeaders(headers(format, userAgent)))
|
|
84
|
+
|
|
85
|
+
const assertHttpUrl = (url: URL) => {
|
|
86
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("URL must use http:// or https://")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const execute = (http: HttpClient.HttpClient, url: string, format: Format, userAgent = browserUserAgent) =>
|
|
90
|
+
http.execute(request(url, format, userAgent)).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk))
|
|
91
|
+
|
|
92
|
+
const collectBody = (response: HttpClientResponse.HttpClientResponse) =>
|
|
93
|
+
collectBoundedResponseBody(
|
|
94
|
+
response,
|
|
95
|
+
MAX_RESPONSE_BYTES,
|
|
96
|
+
() => new Error(`Response too large (exceeds ${MAX_RESPONSE_BYTES} byte limit)`),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const mimeFrom = (contentType: string) => contentType.split(";", 1)[0]?.trim().toLowerCase() ?? ""
|
|
100
|
+
const isImageAttachment = (mime: string) =>
|
|
101
|
+
mime.startsWith("image/") && mime !== "image/svg+xml" && mime !== "image/vnd.fastbidsheet"
|
|
102
|
+
const isTextualMime = (mime: string) =>
|
|
103
|
+
!mime ||
|
|
104
|
+
mime.startsWith("text/") ||
|
|
105
|
+
mime === "application/json" ||
|
|
106
|
+
mime.endsWith("+json") ||
|
|
107
|
+
mime === "application/xml" ||
|
|
108
|
+
mime.endsWith("+xml") ||
|
|
109
|
+
mime === "application/javascript" ||
|
|
110
|
+
mime === "application/x-javascript"
|
|
111
|
+
const convert = (content: string, contentType: string, format: Format) => {
|
|
112
|
+
if (!contentType.includes("text/html")) return content
|
|
113
|
+
if (format === "markdown") return convertHTMLToMarkdown(content)
|
|
114
|
+
if (format === "text") return extractTextFromHTML(content)
|
|
115
|
+
return content
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const layer = Layer.effectDiscard(
|
|
119
|
+
Effect.gen(function* () {
|
|
120
|
+
const tools = yield* Tools.Service
|
|
121
|
+
const http = yield* HttpClient.HttpClient
|
|
122
|
+
const permission = yield* PermissionV2.Service
|
|
123
|
+
|
|
124
|
+
yield* tools
|
|
125
|
+
.register({
|
|
126
|
+
[name]: Tool.make({
|
|
127
|
+
description,
|
|
128
|
+
input: Input,
|
|
129
|
+
output: Output,
|
|
130
|
+
toModelOutput: ({ output }) => [{ type: "text", text: output.output }],
|
|
131
|
+
execute: (input, context) =>
|
|
132
|
+
Effect.gen(function* () {
|
|
133
|
+
yield* Effect.try({
|
|
134
|
+
try: () => assertHttpUrl(new URL(input.url)),
|
|
135
|
+
catch: (error) => error,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
yield* permission.assert({
|
|
139
|
+
action: name,
|
|
140
|
+
resources: [input.url],
|
|
141
|
+
save: ["*"],
|
|
142
|
+
metadata: input,
|
|
143
|
+
sessionID: context.sessionID,
|
|
144
|
+
agent: context.agent,
|
|
145
|
+
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const { body, contentType } = yield* Effect.gen(function* () {
|
|
149
|
+
const response = yield* execute(http, input.url, input.format).pipe(
|
|
150
|
+
Effect.catchIf(isCloudflareChallenge, () => execute(http, input.url, input.format, "neurocode")),
|
|
151
|
+
)
|
|
152
|
+
const contentType = response.headers["content-type"] || ""
|
|
153
|
+
const mime = mimeFrom(contentType)
|
|
154
|
+
if (isImageAttachment(mime))
|
|
155
|
+
return yield* Effect.fail(new Error(`Unsupported fetched image content type: ${mime}`))
|
|
156
|
+
if (!isTextualMime(mime))
|
|
157
|
+
return yield* Effect.fail(new Error(`Unsupported fetched file content type: ${mime}`))
|
|
158
|
+
return { body: yield* collectBody(response), contentType }
|
|
159
|
+
}).pipe(
|
|
160
|
+
Effect.timeoutOrElse({
|
|
161
|
+
duration: Duration.seconds(input.timeout ?? DEFAULT_TIMEOUT_SECONDS),
|
|
162
|
+
orElse: () => Effect.fail(new Error("Request timed out")),
|
|
163
|
+
}),
|
|
164
|
+
)
|
|
165
|
+
const content = new TextDecoder().decode(body)
|
|
166
|
+
const output = yield* Effect.try({
|
|
167
|
+
try: () => convert(content, contentType, input.format),
|
|
168
|
+
catch: (error) => error,
|
|
169
|
+
})
|
|
170
|
+
return {
|
|
171
|
+
url: input.url,
|
|
172
|
+
contentType,
|
|
173
|
+
format: input.format,
|
|
174
|
+
output,
|
|
175
|
+
}
|
|
176
|
+
}).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to fetch ${input.url}` }))),
|
|
177
|
+
}),
|
|
178
|
+
})
|
|
179
|
+
.pipe(Effect.orDie)
|
|
180
|
+
}),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
export const node = makeLocationNode({
|
|
184
|
+
name: "tool/webfetch",
|
|
185
|
+
layer,
|
|
186
|
+
deps: [ToolRegistry.node, PermissionV2.node, LayerNodePlatform.httpClient],
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
export function extractTextFromHTML(html: string) {
|
|
190
|
+
let text = ""
|
|
191
|
+
let skipDepth = 0
|
|
192
|
+
const parser = new Parser({
|
|
193
|
+
onopentag(name) {
|
|
194
|
+
if (skipDepth > 0 || ["script", "style", "noscript", "iframe", "object", "embed"].includes(name)) skipDepth++
|
|
195
|
+
},
|
|
196
|
+
ontext(input) {
|
|
197
|
+
if (skipDepth === 0) text += input
|
|
198
|
+
},
|
|
199
|
+
onclosetag() {
|
|
200
|
+
if (skipDepth > 0) skipDepth--
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
parser.write(html)
|
|
204
|
+
parser.end()
|
|
205
|
+
return text.trim()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function convertHTMLToMarkdown(html: string) {
|
|
209
|
+
const turndown = new TurndownService({
|
|
210
|
+
headingStyle: "atx",
|
|
211
|
+
hr: "---",
|
|
212
|
+
bulletListMarker: "-",
|
|
213
|
+
codeBlockStyle: "fenced",
|
|
214
|
+
emDelimiter: "*",
|
|
215
|
+
})
|
|
216
|
+
turndown.remove(["script", "style", "meta", "link"])
|
|
217
|
+
return turndown.turndown(html)
|
|
218
|
+
}
|