effect 4.1.0 → 4.1.1
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/README.md +30 -0
- package/THIRD_PARTY_NOTICES.md +62 -0
- package/dist/BigDecimal.d.ts +139 -7
- package/dist/BigDecimal.d.ts.map +1 -1
- package/dist/BigDecimal.js +83 -14
- package/dist/BigDecimal.js.map +1 -1
- package/dist/Equal.d.ts +2 -2
- package/dist/Equal.d.ts.map +1 -1
- package/dist/Equal.js +3 -3
- package/dist/Equal.js.map +1 -1
- package/dist/Hash.d.ts +3 -2
- package/dist/Hash.d.ts.map +1 -1
- package/dist/Hash.js +5 -4
- package/dist/Hash.js.map +1 -1
- package/dist/Iterable.d.ts.map +1 -1
- package/dist/Iterable.js +11 -8
- package/dist/Iterable.js.map +1 -1
- package/dist/Path.d.ts.map +1 -1
- package/dist/Path.js +5 -1
- package/dist/Path.js.map +1 -1
- package/dist/Pool.d.ts.map +1 -1
- package/dist/Pool.js +2 -1
- package/dist/Pool.js.map +1 -1
- package/dist/Schema.d.ts +2 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +2 -1
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaRepresentation.d.ts +4 -2
- package/dist/SchemaRepresentation.d.ts.map +1 -1
- package/dist/SchemaRepresentation.js +4 -2
- package/dist/SchemaRepresentation.js.map +1 -1
- package/dist/SchemaTransformation.d.ts +2 -2
- package/dist/SchemaTransformation.js +2 -2
- package/dist/ai/McpClient.d.ts +171 -19
- package/dist/ai/McpClient.d.ts.map +1 -1
- package/dist/ai/McpClient.js +277 -66
- package/dist/ai/McpClient.js.map +1 -1
- package/dist/ai/McpSchema.d.ts +108 -0
- package/dist/ai/McpSchema.d.ts.map +1 -1
- package/dist/ai/McpSchema.js +99 -0
- package/dist/ai/McpSchema.js.map +1 -1
- package/dist/ai/McpServer.d.ts +120 -6
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +215 -34
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Prompt.d.ts.map +1 -1
- package/dist/ai/Prompt.js +20 -11
- package/dist/ai/Prompt.js.map +1 -1
- package/dist/ai/Tool.d.ts.map +1 -1
- package/dist/ai/Tool.js +5 -7
- package/dist/ai/Tool.js.map +1 -1
- package/dist/ai/internal/mcpCore.js +72 -3
- package/dist/ai/internal/mcpCore.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js +3 -1
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js +3 -1
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js +3 -1
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js +3 -1
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js +42 -7
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
- package/dist/ai/internal/mcpProtocol.js +12 -0
- package/dist/ai/internal/mcpProtocol.js.map +1 -1
- package/dist/ai/internal/mcpRuntime.js +3 -3
- package/dist/ai/internal/mcpRuntime.js.map +1 -1
- package/dist/ai/internal/mcpSkills.d.ts +2 -0
- package/dist/ai/internal/mcpSkills.d.ts.map +1 -0
- package/dist/ai/internal/mcpSkills.js +428 -0
- package/dist/ai/internal/mcpSkills.js.map +1 -0
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +15 -5
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/Reply.d.ts +1 -1
- package/dist/cluster/Reply.d.ts.map +1 -1
- package/dist/cluster/Reply.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +4 -1
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +15 -7
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/crypto/Aead.d.ts +2 -1
- package/dist/crypto/Aead.d.ts.map +1 -1
- package/dist/crypto/Aead.js +2 -2
- package/dist/crypto/Aead.js.map +1 -1
- package/dist/crypto/Cipher.d.ts +2 -1
- package/dist/crypto/Cipher.d.ts.map +1 -1
- package/dist/crypto/Cipher.js +2 -2
- package/dist/crypto/Cipher.js.map +1 -1
- package/dist/crypto/KeyAgreement.d.ts +3 -2
- package/dist/crypto/KeyAgreement.d.ts.map +1 -1
- package/dist/crypto/KeyAgreement.js +2 -2
- package/dist/crypto/KeyAgreement.js.map +1 -1
- package/dist/crypto/KeyFormat.d.ts +33 -1
- package/dist/crypto/KeyFormat.d.ts.map +1 -1
- package/dist/crypto/KeyFormat.js.map +1 -1
- package/dist/crypto/KeyWrap.d.ts +4 -3
- package/dist/crypto/KeyWrap.d.ts.map +1 -1
- package/dist/crypto/KeyWrap.js +2 -2
- package/dist/crypto/KeyWrap.js.map +1 -1
- package/dist/crypto/Mac.d.ts +2 -1
- package/dist/crypto/Mac.d.ts.map +1 -1
- package/dist/crypto/Mac.js +2 -2
- package/dist/crypto/Mac.js.map +1 -1
- package/dist/crypto/Signature.d.ts +3 -2
- package/dist/crypto/Signature.d.ts.map +1 -1
- package/dist/crypto/Signature.js +2 -2
- package/dist/crypto/Signature.js.map +1 -1
- package/dist/crypto/WebCrypto.d.ts +19 -0
- package/dist/crypto/WebCrypto.d.ts.map +1 -1
- package/dist/crypto/WebCrypto.js.map +1 -1
- package/dist/crypto/internal/asymmetric.js.map +1 -1
- package/dist/crypto/internal/key.js +3 -2
- package/dist/crypto/internal/key.js.map +1 -1
- package/dist/encoding/Base64Url.d.ts.map +1 -1
- package/dist/encoding/Base64Url.js +26 -3
- package/dist/encoding/Base64Url.js.map +1 -1
- package/dist/encoding/Ndjson.d.ts +5 -4
- package/dist/encoding/Ndjson.d.ts.map +1 -1
- package/dist/encoding/Ndjson.js +6 -5
- package/dist/encoding/Ndjson.js.map +1 -1
- package/dist/encoding/Sse.d.ts +3 -1
- package/dist/encoding/Sse.d.ts.map +1 -1
- package/dist/encoding/Sse.js +5 -3
- package/dist/encoding/Sse.js.map +1 -1
- package/dist/eventlog/EventLog.d.ts +7 -0
- package/dist/eventlog/EventLog.d.ts.map +1 -1
- package/dist/eventlog/EventLog.js +7 -0
- package/dist/eventlog/EventLog.js.map +1 -1
- package/dist/eventlog/EventLogServer.d.ts +7 -1
- package/dist/eventlog/EventLogServer.d.ts.map +1 -1
- package/dist/eventlog/EventLogServer.js +27 -12
- package/dist/eventlog/EventLogServer.js.map +1 -1
- package/dist/eventlog/EventLogServerEncrypted.d.ts +10 -0
- package/dist/eventlog/EventLogServerEncrypted.d.ts.map +1 -1
- package/dist/eventlog/EventLogServerEncrypted.js.map +1 -1
- package/dist/eventlog/EventLogServerUnencrypted.d.ts +17 -0
- package/dist/eventlog/EventLogServerUnencrypted.d.ts.map +1 -1
- package/dist/eventlog/EventLogServerUnencrypted.js +14 -2
- package/dist/eventlog/EventLogServerUnencrypted.js.map +1 -1
- package/dist/eventlog/EventLogSessionAuth.d.ts +21 -0
- package/dist/eventlog/EventLogSessionAuth.d.ts.map +1 -1
- package/dist/eventlog/EventLogSessionAuth.js +22 -0
- package/dist/eventlog/EventLogSessionAuth.js.map +1 -1
- package/dist/graphql/GraphQLClient.d.ts +3 -1
- package/dist/graphql/GraphQLClient.d.ts.map +1 -1
- package/dist/graphql/GraphQLClient.js.map +1 -1
- package/dist/graphql/GraphQLClientError.d.ts +14 -9
- package/dist/graphql/GraphQLClientError.d.ts.map +1 -1
- package/dist/graphql/GraphQLClientError.js +14 -21
- package/dist/graphql/GraphQLClientError.js.map +1 -1
- package/dist/graphql/GraphQLProtocol.d.ts +12 -3
- package/dist/graphql/GraphQLProtocol.d.ts.map +1 -1
- package/dist/graphql/GraphQLProtocol.js +4 -1
- package/dist/graphql/GraphQLProtocol.js.map +1 -1
- package/dist/http/HttpClient.d.ts +189 -7
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +75 -2
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpServerRequest.d.ts.map +1 -1
- package/dist/http/HttpServerRequest.js +38 -22
- package/dist/http/HttpServerRequest.js.map +1 -1
- package/dist/http/internal/mimeTypes.d.ts +4 -1
- package/dist/http/internal/mimeTypes.d.ts.map +1 -1
- package/dist/http/internal/mimeTypes.js +4 -1
- package/dist/http/internal/mimeTypes.js.map +1 -1
- package/dist/http-api/HttpApiSchema.js +1 -1
- package/dist/http-api/HttpApiSchema.js.map +1 -1
- package/dist/internal/dateTime.js +49 -24
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/effect.js +10 -0
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/glob.js +234 -56
- package/dist/internal/glob.js.map +1 -1
- package/dist/internal/graphql.js +21 -7
- package/dist/internal/graphql.js.map +1 -1
- package/dist/internal/graphqlHeaders.d.ts +7 -0
- package/dist/internal/graphqlHeaders.d.ts.map +1 -1
- package/dist/internal/graphqlHeaders.js +11 -0
- package/dist/internal/graphqlHeaders.js.map +1 -1
- package/dist/internal/request.js +29 -42
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/schema/fromJsonSchemaDocument.js +7 -8
- package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/jose/JoseError.d.ts +285 -0
- package/dist/jose/JoseError.d.ts.map +1 -0
- package/dist/jose/JoseError.js +269 -0
- package/dist/jose/JoseError.js.map +1 -0
- package/dist/jose/Jwe.d.ts +128 -0
- package/dist/jose/Jwe.d.ts.map +1 -0
- package/dist/jose/Jwe.js +134 -0
- package/dist/jose/Jwe.js.map +1 -0
- package/dist/jose/Jwk.d.ts +597 -0
- package/dist/jose/Jwk.d.ts.map +1 -0
- package/dist/jose/Jwk.js +503 -0
- package/dist/jose/Jwk.js.map +1 -0
- package/dist/jose/Jwks.d.ts +236 -0
- package/dist/jose/Jwks.d.ts.map +1 -0
- package/dist/jose/Jwks.js +306 -0
- package/dist/jose/Jwks.js.map +1 -0
- package/dist/jose/Jws.d.ts +177 -0
- package/dist/jose/Jws.d.ts.map +1 -0
- package/dist/jose/Jws.js +183 -0
- package/dist/jose/Jws.js.map +1 -0
- package/dist/jose/Jwt.d.ts +270 -0
- package/dist/jose/Jwt.d.ts.map +1 -0
- package/dist/jose/Jwt.js +293 -0
- package/dist/jose/Jwt.js.map +1 -0
- package/dist/jose/index.d.ts +51 -0
- package/dist/jose/index.d.ts.map +1 -0
- package/dist/jose/index.js +52 -0
- package/dist/jose/index.js.map +1 -0
- package/dist/jose/internal/compact.d.ts +2 -0
- package/dist/jose/internal/compact.d.ts.map +1 -0
- package/dist/jose/internal/compact.js +179 -0
- package/dist/jose/internal/compact.js.map +1 -0
- package/dist/net/Host.d.ts.map +1 -1
- package/dist/net/Host.js +5 -10
- package/dist/net/Host.js.map +1 -1
- package/dist/observability/OtlpSerialization.d.ts +1 -1
- package/dist/observability/OtlpSerialization.d.ts.map +1 -1
- package/dist/observability/OtlpSerialization.js +10 -4
- package/dist/observability/OtlpSerialization.js.map +1 -1
- package/dist/observability/internal/otlpProtobuf.js +3 -3
- package/dist/observability/internal/otlpProtobuf.js.map +1 -1
- package/dist/observability/internal/protobuf.js +6 -0
- package/dist/observability/internal/protobuf.js.map +1 -1
- package/dist/persistence/CircuitBreaker.d.ts +98 -38
- package/dist/persistence/CircuitBreaker.d.ts.map +1 -1
- package/dist/persistence/CircuitBreaker.js +67 -29
- package/dist/persistence/CircuitBreaker.js.map +1 -1
- package/dist/persistence/RateLimiter.d.ts +8 -0
- package/dist/persistence/RateLimiter.d.ts.map +1 -1
- package/dist/persistence/RateLimiter.js +63 -12
- package/dist/persistence/RateLimiter.js.map +1 -1
- package/dist/persistence/index.d.ts +1 -1
- package/dist/persistence/index.js +1 -1
- package/dist/reactivity/AtomRegistry.js +17 -7
- package/dist/reactivity/AtomRegistry.js.map +1 -1
- package/dist/rpc/Rpc.d.ts +1 -1
- package/dist/rpc/Rpc.d.ts.map +1 -1
- package/dist/rpc/Rpc.js.map +1 -1
- package/dist/rpc/RpcClient.d.ts +14 -2
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +38 -21
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcMessage.d.ts +3 -2
- package/dist/rpc/RpcMessage.d.ts.map +1 -1
- package/dist/rpc/RpcMessage.js.map +1 -1
- package/dist/sql/Migrator.d.ts +9 -1
- package/dist/sql/Migrator.d.ts.map +1 -1
- package/dist/sql/Migrator.js +18 -6
- package/dist/sql/Migrator.js.map +1 -1
- package/dist/sql/SqlClient.d.ts +38 -3
- package/dist/sql/SqlClient.d.ts.map +1 -1
- package/dist/sql/SqlClient.js +4 -3
- package/dist/sql/SqlClient.js.map +1 -1
- package/dist/sql/Statement.d.ts +41 -3
- package/dist/sql/Statement.d.ts.map +1 -1
- package/dist/sql/Statement.js +24 -20
- package/dist/sql/Statement.js.map +1 -1
- package/dist/workflow/Workflow.d.ts +4 -0
- package/dist/workflow/Workflow.d.ts.map +1 -1
- package/dist/workflow/Workflow.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +5 -0
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +5 -1
- package/src/BigDecimal.ts +170 -16
- package/src/Equal.ts +3 -3
- package/src/Hash.ts +5 -4
- package/src/Iterable.ts +8 -5
- package/src/Path.ts +6 -1
- package/src/Pool.ts +2 -1
- package/src/Schema.ts +3 -2
- package/src/SchemaRepresentation.ts +4 -2
- package/src/SchemaTransformation.ts +2 -2
- package/src/ai/McpClient.ts +488 -91
- package/src/ai/McpSchema.ts +119 -0
- package/src/ai/McpServer.ts +261 -38
- package/src/ai/Prompt.ts +16 -11
- package/src/ai/Tool.ts +5 -10
- package/src/ai/internal/mcpCore.ts +165 -12
- package/src/ai/internal/mcpProtocol/v2024_11_05.ts +3 -1
- package/src/ai/internal/mcpProtocol/v2025_03_26.ts +3 -1
- package/src/ai/internal/mcpProtocol/v2025_06_18.ts +3 -1
- package/src/ai/internal/mcpProtocol/v2025_11_25.ts +3 -1
- package/src/ai/internal/mcpProtocol/v2026_07_28.ts +41 -7
- package/src/ai/internal/mcpProtocol.ts +15 -0
- package/src/ai/internal/mcpRuntime.ts +6 -3
- package/src/ai/internal/mcpSkills.ts +480 -0
- package/src/cluster/ClusterWorkflowEngine.ts +22 -8
- package/src/cluster/Reply.ts +1 -1
- package/src/cluster/Sharding.ts +4 -1
- package/src/cluster/internal/entityManager.ts +24 -10
- package/src/crypto/Aead.ts +3 -3
- package/src/crypto/Cipher.ts +3 -3
- package/src/crypto/KeyAgreement.ts +4 -4
- package/src/crypto/KeyFormat.ts +30 -1
- package/src/crypto/KeyWrap.ts +6 -6
- package/src/crypto/Mac.ts +3 -3
- package/src/crypto/Signature.ts +4 -4
- package/src/crypto/WebCrypto.ts +20 -0
- package/src/crypto/internal/asymmetric.ts +2 -1
- package/src/crypto/internal/key.ts +12 -5
- package/src/encoding/Base64Url.ts +27 -3
- package/src/encoding/Ndjson.ts +6 -5
- package/src/encoding/Sse.ts +5 -3
- package/src/eventlog/EventLog.ts +7 -0
- package/src/eventlog/EventLogServer.ts +31 -12
- package/src/eventlog/EventLogServerEncrypted.ts +5 -0
- package/src/eventlog/EventLogServerUnencrypted.ts +19 -2
- package/src/eventlog/EventLogSessionAuth.ts +26 -0
- package/src/graphql/GraphQLClient.ts +3 -1
- package/src/graphql/GraphQLClientError.ts +15 -22
- package/src/graphql/GraphQLProtocol.ts +12 -3
- package/src/http/HttpClient.ts +210 -11
- package/src/http/HttpServerRequest.ts +61 -23
- package/src/http/internal/mimeTypes.ts +4 -1
- package/src/http-api/HttpApiSchema.ts +1 -1
- package/src/internal/dateTime.ts +54 -24
- package/src/internal/effect.ts +6 -0
- package/src/internal/glob.ts +279 -63
- package/src/internal/graphql.ts +27 -9
- package/src/internal/graphqlHeaders.ts +13 -0
- package/src/internal/request.ts +51 -58
- package/src/internal/schema/fromJsonSchemaDocument.ts +11 -10
- package/src/internal/version.ts +1 -1
- package/src/jose/JoseError.ts +329 -0
- package/src/jose/Jwe.ts +196 -0
- package/src/jose/Jwk.ts +777 -0
- package/src/jose/Jwks.ts +418 -0
- package/src/jose/Jws.ts +312 -0
- package/src/jose/Jwt.ts +493 -0
- package/src/jose/index.ts +58 -0
- package/src/jose/internal/compact.ts +195 -0
- package/src/net/Host.ts +5 -10
- package/src/observability/OtlpSerialization.ts +13 -5
- package/src/observability/internal/otlpProtobuf.ts +6 -6
- package/src/observability/internal/protobuf.ts +10 -0
- package/src/persistence/CircuitBreaker.ts +113 -48
- package/src/persistence/RateLimiter.ts +84 -15
- package/src/persistence/index.ts +1 -1
- package/src/reactivity/AtomRegistry.ts +19 -8
- package/src/rpc/Rpc.ts +3 -1
- package/src/rpc/RpcClient.ts +44 -24
- package/src/rpc/RpcMessage.ts +3 -2
- package/src/sql/Migrator.ts +35 -18
- package/src/sql/SqlClient.ts +43 -8
- package/src/sql/Statement.ts +52 -22
- package/src/workflow/Workflow.ts +4 -0
- package/src/workflow/WorkflowEngine.ts +5 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Skills extension (SEP-2640): its wire vocabulary, the catalog behind a
|
|
3
|
+
* static skill, and the handlers shared by the sessionful adapters.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
import * as Cause from "../../Cause.ts"
|
|
8
|
+
import * as Digest from "../../crypto/Digest.ts"
|
|
9
|
+
import * as Effect from "../../Effect.ts"
|
|
10
|
+
import * as Yaml from "../../encoding/Yaml.ts"
|
|
11
|
+
import * as Mime from "../../http/Mime.ts"
|
|
12
|
+
import * as Option from "../../Option.ts"
|
|
13
|
+
import * as Predicate from "../../Predicate.ts"
|
|
14
|
+
import * as Rpc from "../../rpc/Rpc.ts"
|
|
15
|
+
import * as RpcGroup from "../../rpc/RpcGroup.ts"
|
|
16
|
+
import * as Schema from "../../Schema.ts"
|
|
17
|
+
import * as PublicMcpSchema from "../McpSchema.ts"
|
|
18
|
+
import type * as McpCore from "./mcpCore.ts"
|
|
19
|
+
import * as McpProtocol from "./mcpProtocol.ts"
|
|
20
|
+
import * as Legacy from "./mcpSchema/v2024_11_05.ts"
|
|
21
|
+
import * as Stateless from "./mcpSchema/v2026_07_28.ts"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export const EXTENSION_ID = "io.modelcontextprotocol/skills"
|
|
27
|
+
|
|
28
|
+
const DIRECTORY_MIME_TYPE = "inode/directory"
|
|
29
|
+
|
|
30
|
+
// The extension's per-skill limits, which every conforming host accepts.
|
|
31
|
+
const MAX_FILES_PER_SKILL = 512
|
|
32
|
+
const MAX_BYTES_PER_SKILL = 16 * 1024 * 1024
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Adds the extension declaration while any skill is registered. Declaring it
|
|
36
|
+
* commits the server to `skills/list`, `skills/get` and, through
|
|
37
|
+
* `directoryRead`, `resources/directory/read`.
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export const declareExtension = <A>(
|
|
42
|
+
extensions: Readonly<Record<string, A>> | undefined,
|
|
43
|
+
servesSkills: boolean
|
|
44
|
+
): Readonly<Record<string, A | { readonly directoryRead: true }>> | undefined =>
|
|
45
|
+
servesSkills ? { ...extensions, [EXTENSION_ID]: { directoryRead: true } } : extensions
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// Wire schemas
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
const Skill = Schema.Struct({
|
|
52
|
+
uri: Schema.String,
|
|
53
|
+
frontmatter: Legacy.UnknownObject,
|
|
54
|
+
resources: Schema.Union([
|
|
55
|
+
Schema.Array(Schema.Struct(PublicMcpSchema.SkillResource.fields)),
|
|
56
|
+
Schema.Literal("dynamic")
|
|
57
|
+
])
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Listed entries are checked one at a time, so that a client can leave out an
|
|
61
|
+
// invalid entry instead of rejecting the whole page.
|
|
62
|
+
const ListedSkills = Schema.Array(Schema.Unknown)
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export class LegacyListSkills extends Rpc.make("skills/list", {
|
|
68
|
+
success: Schema.Struct({ ...Legacy.PaginatedResult.fields, skills: ListedSkills }),
|
|
69
|
+
error: Legacy.McpError,
|
|
70
|
+
payload: Schema.UndefinedOr(Legacy.PaginatedRequest)
|
|
71
|
+
}) {}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
export class LegacyGetSkill extends Rpc.make("skills/get", {
|
|
77
|
+
success: Schema.Struct({ ...Legacy.ResultMeta.fields, skill: Skill }),
|
|
78
|
+
error: Legacy.McpError,
|
|
79
|
+
payload: { ...Legacy.RequestMeta.fields, uri: Schema.String }
|
|
80
|
+
}) {}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @internal
|
|
84
|
+
*/
|
|
85
|
+
export class LegacyReadResourceDirectory extends Rpc.make("resources/directory/read", {
|
|
86
|
+
success: Schema.Struct({ ...Legacy.PaginatedResult.fields, resources: Schema.Array(Legacy.Resource) }),
|
|
87
|
+
error: Legacy.McpError,
|
|
88
|
+
payload: { ...Legacy.PaginatedRequest.fields, uri: Schema.String }
|
|
89
|
+
}) {}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The extension's methods on revisions before 2026-07-28, which have no
|
|
93
|
+
* result caching attributes.
|
|
94
|
+
*
|
|
95
|
+
* @internal
|
|
96
|
+
*/
|
|
97
|
+
export class LegacyRpcs extends RpcGroup.make(LegacyListSkills, LegacyGetSkill, LegacyReadResourceDirectory) {}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @internal
|
|
101
|
+
*/
|
|
102
|
+
export const ListSkillsResult = Schema.StructWithRest(
|
|
103
|
+
Schema.Struct({ ...Stateless.PaginatedResult, ...Stateless.CacheableResult, skills: ListedSkills }),
|
|
104
|
+
[Stateless.UnknownObject]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @internal
|
|
109
|
+
*/
|
|
110
|
+
export class ListSkills extends Rpc.make("skills/list", {
|
|
111
|
+
success: ListSkillsResult,
|
|
112
|
+
error: Stateless.McpError,
|
|
113
|
+
payload: Stateless.PaginatedRequestParams
|
|
114
|
+
}) {}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @internal
|
|
118
|
+
*/
|
|
119
|
+
export const GetSkillResult = Schema.StructWithRest(
|
|
120
|
+
Schema.Struct({ ...Stateless.CacheableResult, skill: Skill }),
|
|
121
|
+
[Stateless.UnknownObject]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @internal
|
|
126
|
+
*/
|
|
127
|
+
export class GetSkill extends Rpc.make("skills/get", {
|
|
128
|
+
success: GetSkillResult,
|
|
129
|
+
error: Stateless.McpError,
|
|
130
|
+
payload: { ...Stateless.RequestParams.fields, uri: Schema.String }
|
|
131
|
+
}) {}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
136
|
+
export const ReadResourceDirectoryResult = Schema.StructWithRest(
|
|
137
|
+
Schema.Struct({
|
|
138
|
+
...Stateless.PaginatedResult,
|
|
139
|
+
...Stateless.CacheableResult,
|
|
140
|
+
resources: Schema.Array(Stateless.Resource)
|
|
141
|
+
}),
|
|
142
|
+
[Stateless.UnknownObject]
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @internal
|
|
147
|
+
*/
|
|
148
|
+
export class ReadResourceDirectory extends Rpc.make("resources/directory/read", {
|
|
149
|
+
success: ReadResourceDirectoryResult,
|
|
150
|
+
error: Stateless.McpError,
|
|
151
|
+
payload: { ...Stateless.PaginatedRequestParams, uri: Schema.String }
|
|
152
|
+
}) {}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The extension's methods on revision 2026-07-28.
|
|
156
|
+
*
|
|
157
|
+
* @internal
|
|
158
|
+
*/
|
|
159
|
+
export class StatelessRpcs extends RpcGroup.make(ListSkills, GetSkill, ReadResourceDirectory) {}
|
|
160
|
+
|
|
161
|
+
// =============================================================================
|
|
162
|
+
// Handlers
|
|
163
|
+
// =============================================================================
|
|
164
|
+
|
|
165
|
+
const methodNotFound = (method: string) =>
|
|
166
|
+
new McpProtocol.ProtocolError({
|
|
167
|
+
code: PublicMcpSchema.METHOD_NOT_FOUND_ERROR_CODE,
|
|
168
|
+
message: `Method not found: ${method}`
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// Every skill fits on one page, so the server never issues a cursor.
|
|
172
|
+
const unknownCursor = new McpProtocol.ProtocolError({
|
|
173
|
+
code: PublicMcpSchema.INVALID_PARAMS_ERROR_CODE,
|
|
174
|
+
message: "Unknown cursor"
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Fails with `Method not found` while no skill is registered, as a server
|
|
179
|
+
* that does not declare the extension.
|
|
180
|
+
*
|
|
181
|
+
* @internal
|
|
182
|
+
*/
|
|
183
|
+
export const requireSkills = (core: McpCore.McpCore, method: string) =>
|
|
184
|
+
Effect.flatMap(
|
|
185
|
+
core.registrationPresence,
|
|
186
|
+
(presence) => presence.skills ? Effect.void : Effect.fail(methodNotFound(method))
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @internal
|
|
191
|
+
*/
|
|
192
|
+
export const rejectCursor = (cursor: string | undefined) =>
|
|
193
|
+
cursor === undefined ? Effect.void : Effect.fail(unknownCursor)
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Handlers for {@link LegacyRpcs}, for the sessionful adapters.
|
|
197
|
+
*
|
|
198
|
+
* @internal
|
|
199
|
+
*/
|
|
200
|
+
export const legacyHandlers = (core: McpCore.McpCore) => ({
|
|
201
|
+
"skills/list": Effect.fnUntraced(function*(request: typeof LegacyListSkills.payloadSchema.Type) {
|
|
202
|
+
yield* requireSkills(core, "skills/list")
|
|
203
|
+
yield* rejectCursor(request?.cursor)
|
|
204
|
+
const skills = yield* McpProtocol.listForClient(core.skills.list)
|
|
205
|
+
return { skills }
|
|
206
|
+
}),
|
|
207
|
+
"skills/get": Effect.fnUntraced(function*(request: typeof LegacyGetSkill.payloadSchema.Type) {
|
|
208
|
+
yield* requireSkills(core, "skills/get")
|
|
209
|
+
const client = yield* PublicMcpSchema.McpServerClient
|
|
210
|
+
const skill = yield* core.skills.get(request.uri, McpProtocol.invocationFromClient(client)).pipe(
|
|
211
|
+
Effect.mapError(McpProtocol.ProtocolError.fromFeature)
|
|
212
|
+
)
|
|
213
|
+
return { skill }
|
|
214
|
+
}),
|
|
215
|
+
"resources/directory/read": Effect.fnUntraced(function*(
|
|
216
|
+
request: typeof LegacyReadResourceDirectory.payloadSchema.Type
|
|
217
|
+
) {
|
|
218
|
+
yield* requireSkills(core, "resources/directory/read")
|
|
219
|
+
yield* rejectCursor(request.cursor)
|
|
220
|
+
const client = yield* PublicMcpSchema.McpServerClient
|
|
221
|
+
const resources = yield* core.skills.readDirectory(request.uri, McpProtocol.invocationFromClient(client)).pipe(
|
|
222
|
+
Effect.mapError(McpProtocol.ProtocolError.fromFeature)
|
|
223
|
+
)
|
|
224
|
+
return { resources }
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
// =============================================================================
|
|
229
|
+
// Static skills
|
|
230
|
+
// =============================================================================
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* A file of a static skill as handed in, `SKILL.md` included, with its path
|
|
234
|
+
* relative to the skill root.
|
|
235
|
+
*
|
|
236
|
+
* @internal
|
|
237
|
+
*/
|
|
238
|
+
export interface SourceFile {
|
|
239
|
+
readonly path: string
|
|
240
|
+
readonly content: Uint8Array
|
|
241
|
+
readonly mimeType?: string | undefined
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* A file of a static skill as served through `resources/read`.
|
|
246
|
+
*
|
|
247
|
+
* @internal
|
|
248
|
+
*/
|
|
249
|
+
export interface ServedFile {
|
|
250
|
+
// Listed by `resources/list` and as a child on `resources/directory/read`.
|
|
251
|
+
readonly resource: McpCore.DirectoryChild
|
|
252
|
+
readonly content: Uint8Array
|
|
253
|
+
// Set when the file is textual and valid UTF-8; otherwise it is served as a blob.
|
|
254
|
+
readonly text: string | undefined
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* @internal
|
|
259
|
+
*/
|
|
260
|
+
export interface StaticSkill {
|
|
261
|
+
readonly entry: McpCore.Skill
|
|
262
|
+
readonly files: ReadonlyArray<ServedFile>
|
|
263
|
+
// Directory URI to its direct children.
|
|
264
|
+
readonly directories: ReadonlyMap<string, ReadonlyArray<McpCore.DirectoryChild>>
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const TEXTUAL_APPLICATION_TYPES: ReadonlySet<string> = new Set([
|
|
268
|
+
"application/json",
|
|
269
|
+
"application/yaml",
|
|
270
|
+
"image/svg+xml"
|
|
271
|
+
])
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @internal
|
|
275
|
+
*/
|
|
276
|
+
export const mimeTypeFor = (path: string): string => {
|
|
277
|
+
// The standard table maps `.ts` to MPEG transport streams, but a skill's
|
|
278
|
+
// `.ts` files are TypeScript scripts.
|
|
279
|
+
if (/\.ts$/i.test(path)) return "text/x-typescript"
|
|
280
|
+
return Option.getOrElse(Mime.getType(path), () => "application/octet-stream")
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// `ignoreBOM` keeps a leading BOM in the decoded text, so re-encoding the text
|
|
284
|
+
// reproduces exactly the bytes the manifest digest covers.
|
|
285
|
+
const utf8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true })
|
|
286
|
+
|
|
287
|
+
const decodeText = (mimeType: string, content: Uint8Array): string | undefined => {
|
|
288
|
+
if (!mimeType.startsWith("text/") && !TEXTUAL_APPLICATION_TYPES.has(mimeType)) {
|
|
289
|
+
return undefined
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
return utf8.decode(content)
|
|
293
|
+
} catch {
|
|
294
|
+
return undefined
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const isSafeRelativePath = (path: string): boolean =>
|
|
299
|
+
path !== "" &&
|
|
300
|
+
!path.includes("\\") &&
|
|
301
|
+
path.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..")
|
|
302
|
+
|
|
303
|
+
// The Agent Skills `name` rule: lowercase letters, digits and single hyphens,
|
|
304
|
+
// neither leading nor trailing, at most 64 characters.
|
|
305
|
+
const isSkillName = (name: string): boolean => name.length <= 64 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)
|
|
306
|
+
|
|
307
|
+
const FRONTMATTER = /^\uFEFF?---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* The YAML frontmatter of a `SKILL.md`, or `undefined` when the file does not
|
|
311
|
+
* begin with a frontmatter block holding a YAML mapping.
|
|
312
|
+
*
|
|
313
|
+
* @internal
|
|
314
|
+
*/
|
|
315
|
+
export const parseFrontmatter = (markdown: string): Record<string, unknown> | undefined => {
|
|
316
|
+
const block = FRONTMATTER.exec(markdown)?.[1]
|
|
317
|
+
if (block === undefined) return undefined
|
|
318
|
+
try {
|
|
319
|
+
const parsed = Yaml.parse(block)
|
|
320
|
+
// The frontmatter is listed as JSON, so keep only what JSON carries:
|
|
321
|
+
// `.inf` and `.nan` become `null`.
|
|
322
|
+
return Predicate.isObject(parsed) ? JSON.parse(JSON.stringify(parsed)) : undefined
|
|
323
|
+
} catch {
|
|
324
|
+
return undefined
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// The directory holding a skill's `SKILL.md`, given the `SKILL.md` URI.
|
|
329
|
+
const skillRoot = (uri: string): string => uri.slice(0, -"/SKILL.md".length)
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Whether `uri` addresses a file inside the skill whose `SKILL.md` is at
|
|
333
|
+
* `skillUri`.
|
|
334
|
+
*
|
|
335
|
+
* @internal
|
|
336
|
+
*/
|
|
337
|
+
export const isSkillFile = (skillUri: string, uri: string): boolean => {
|
|
338
|
+
const root = skillRoot(skillUri)
|
|
339
|
+
return uri.startsWith(`${root}/`) && isSafeRelativePath(uri.slice(root.length + 1))
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Why a skill entry received from a server breaks the extension's rules, or
|
|
344
|
+
* `undefined` when it is valid. Hosts must not load an invalid entry. The
|
|
345
|
+
* digest and size formats are checked by {@link PublicMcpSchema.SkillResource}.
|
|
346
|
+
*
|
|
347
|
+
* @internal
|
|
348
|
+
*/
|
|
349
|
+
export const entryProblem = (entry: McpCore.Skill): string | undefined => {
|
|
350
|
+
const schemeEnd = entry.uri.indexOf("://")
|
|
351
|
+
if (schemeEnd <= 0 || !entry.uri.endsWith("/SKILL.md")) return "the URI does not address a SKILL.md"
|
|
352
|
+
const root = skillRoot(entry.uri)
|
|
353
|
+
const skillPath = root.slice(schemeEnd + 3)
|
|
354
|
+
if (!isSafeRelativePath(skillPath)) return "the skill path has an empty, '.' or '..' segment"
|
|
355
|
+
const name = skillPath.split("/").at(-1)!
|
|
356
|
+
if (entry.frontmatter.name !== name || !isSkillName(name)) {
|
|
357
|
+
return `the frontmatter name must equal "${name}" and follow the Agent Skills naming rules`
|
|
358
|
+
}
|
|
359
|
+
const description = entry.frontmatter.description
|
|
360
|
+
if (typeof description !== "string" || description === "" || description.length > 1024) {
|
|
361
|
+
return "the frontmatter description must be a string of 1 to 1024 characters"
|
|
362
|
+
}
|
|
363
|
+
if (entry.resources === "dynamic") return undefined
|
|
364
|
+
const uris = new Set<string>()
|
|
365
|
+
for (const resource of entry.resources) {
|
|
366
|
+
if (!isSkillFile(entry.uri, resource.uri)) return `${resource.uri} is not a file of the skill`
|
|
367
|
+
if (uris.has(resource.uri)) return `${resource.uri} is listed twice`
|
|
368
|
+
uris.add(resource.uri)
|
|
369
|
+
}
|
|
370
|
+
return uris.has(entry.uri) ? undefined : "the manifest does not list SKILL.md"
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Whether `child` is a direct child of the directory at `directory`.
|
|
375
|
+
*
|
|
376
|
+
* @internal
|
|
377
|
+
*/
|
|
378
|
+
export const isDirectChild = (directory: string, child: string): boolean => {
|
|
379
|
+
if (!child.startsWith(`${directory}/`)) return false
|
|
380
|
+
const name = child.slice(directory.length + 1)
|
|
381
|
+
return name !== "" && name !== "." && name !== ".." && !name.includes("/")
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const decodeFrontmatter = Schema.decodeUnknownEffect(PublicMcpSchema.SkillFrontmatter)
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Validates a static skill against the extension's server requirements and
|
|
388
|
+
* precomputes what the protocol serves: the manifest, with digests over the
|
|
389
|
+
* exact bytes served, each file's resource metadata, and the directory tree
|
|
390
|
+
* from the namespace root down.
|
|
391
|
+
*
|
|
392
|
+
* @internal
|
|
393
|
+
*/
|
|
394
|
+
export const makeStaticSkill = (options: {
|
|
395
|
+
readonly uri: string
|
|
396
|
+
readonly files: ReadonlyArray<SourceFile>
|
|
397
|
+
}): Effect.Effect<StaticSkill, Cause.IllegalArgumentError, Digest.Digest> =>
|
|
398
|
+
Effect.gen(function*() {
|
|
399
|
+
const { uri } = options
|
|
400
|
+
const fail = (reason: string) => Effect.fail(new Cause.IllegalArgumentError(`Skill ${uri}: ${reason}`))
|
|
401
|
+
|
|
402
|
+
const schemeEnd = uri.indexOf("://")
|
|
403
|
+
if (schemeEnd <= 0 || !uri.endsWith("/SKILL.md")) {
|
|
404
|
+
return yield* fail("the URI must have the form <scheme>://<skill-path>/SKILL.md")
|
|
405
|
+
}
|
|
406
|
+
const scheme = uri.slice(0, schemeEnd + 3)
|
|
407
|
+
const skillPath = uri.slice(scheme.length, -"/SKILL.md".length)
|
|
408
|
+
if (!isSafeRelativePath(skillPath)) {
|
|
409
|
+
return yield* fail("the skill path must have no empty, '.' or '..' segment")
|
|
410
|
+
}
|
|
411
|
+
const name = skillPath.split("/").at(-1)!
|
|
412
|
+
if (options.files.length > MAX_FILES_PER_SKILL) {
|
|
413
|
+
return yield* fail(`it has ${options.files.length} files; the limit is ${MAX_FILES_PER_SKILL}`)
|
|
414
|
+
}
|
|
415
|
+
const totalBytes = options.files.reduce((sum, file) => sum + file.content.byteLength, 0)
|
|
416
|
+
if (totalBytes > MAX_BYTES_PER_SKILL) {
|
|
417
|
+
return yield* fail(`it has ${totalBytes} bytes; the limit is ${MAX_BYTES_PER_SKILL}`)
|
|
418
|
+
}
|
|
419
|
+
const paths = new Set<string>()
|
|
420
|
+
for (const file of options.files) {
|
|
421
|
+
if (!isSafeRelativePath(file.path)) return yield* fail(`the file path "${file.path}" escapes the skill`)
|
|
422
|
+
if (paths.has(file.path)) return yield* fail(`the file "${file.path}" is given twice`)
|
|
423
|
+
paths.add(file.path)
|
|
424
|
+
}
|
|
425
|
+
const skillMd = options.files.find((file) => file.path === "SKILL.md")
|
|
426
|
+
if (skillMd === undefined) return yield* fail("SKILL.md is missing")
|
|
427
|
+
|
|
428
|
+
// The listed frontmatter is parsed from the served SKILL.md, so the two
|
|
429
|
+
// cannot differ.
|
|
430
|
+
const frontmatter = parseFrontmatter(decodeText("text/markdown", skillMd.content) ?? "")
|
|
431
|
+
if (frontmatter === undefined) return yield* fail("SKILL.md must begin with YAML frontmatter holding a mapping")
|
|
432
|
+
if (frontmatter.name !== name) {
|
|
433
|
+
return yield* fail(`the frontmatter name ${JSON.stringify(frontmatter.name)} must equal "${name}"`)
|
|
434
|
+
}
|
|
435
|
+
// A skill served over MCP must conform to the Agent Skills specification.
|
|
436
|
+
const { description } = yield* decodeFrontmatter(frontmatter).pipe(
|
|
437
|
+
Effect.catch((error) => fail(`the frontmatter is not valid Agent Skills frontmatter: ${error.message}`))
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
const digest = yield* Digest.Digest
|
|
441
|
+
const sorted = [...options.files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
|
|
442
|
+
const resources: Array<McpCore.SkillResource> = []
|
|
443
|
+
const files: Array<ServedFile> = []
|
|
444
|
+
const children = new Map<string, Map<string, McpCore.DirectoryChild>>()
|
|
445
|
+
for (const file of sorted) {
|
|
446
|
+
const fileUri = `${scheme}${skillPath}/${file.path}`
|
|
447
|
+
const fileDigest = `sha256:${yield* digest.digestHex(Digest.sha256, file.content)}`
|
|
448
|
+
const mimeType = file.mimeType ?? mimeTypeFor(file.path)
|
|
449
|
+
const size = file.content.byteLength
|
|
450
|
+
const resource: McpCore.DirectoryChild = file.path === "SKILL.md"
|
|
451
|
+
? { uri: fileUri, name, description, mimeType, size }
|
|
452
|
+
: { uri: fileUri, name: file.path.split("/").at(-1)!, mimeType, size }
|
|
453
|
+
files.push({ resource, content: file.content, text: decodeText(mimeType, file.content) })
|
|
454
|
+
resources.push({ uri: fileUri, digest: fileDigest, size })
|
|
455
|
+
|
|
456
|
+
// Every directory from the namespace root down is readable, including
|
|
457
|
+
// the prefix directories above the skill root.
|
|
458
|
+
const segments = `${skillPath}/${file.path}`.split("/")
|
|
459
|
+
for (let depth = 1; depth < segments.length; depth++) {
|
|
460
|
+
const directory = `${scheme}${segments.slice(0, depth).join("/")}`
|
|
461
|
+
const child: McpCore.DirectoryChild = depth === segments.length - 1 ? resource : {
|
|
462
|
+
uri: `${scheme}${segments.slice(0, depth + 1).join("/")}`,
|
|
463
|
+
name: segments[depth]!,
|
|
464
|
+
mimeType: DIRECTORY_MIME_TYPE
|
|
465
|
+
}
|
|
466
|
+
let siblings = children.get(directory)
|
|
467
|
+
if (siblings === undefined) {
|
|
468
|
+
siblings = new Map()
|
|
469
|
+
children.set(directory, siblings)
|
|
470
|
+
}
|
|
471
|
+
siblings.set(child.uri, child)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const directories = new Map<string, ReadonlyArray<McpCore.DirectoryChild>>()
|
|
476
|
+
for (const [directory, siblings] of children) {
|
|
477
|
+
directories.set(directory, Array.from(siblings.values()))
|
|
478
|
+
}
|
|
479
|
+
return { entry: { uri, frontmatter, resources }, files, directories }
|
|
480
|
+
})
|
|
@@ -535,14 +535,15 @@ export const make = Effect.gen(function*() {
|
|
|
535
535
|
if (instance.abandoned) {
|
|
536
536
|
instance.suspended = false
|
|
537
537
|
}
|
|
538
|
+
// Abandoned runs never wake the parent; another owner replays them.
|
|
539
|
+
const resumeParent = parent && !parent.detached && !instance.abandoned
|
|
540
|
+
? ensureSuccess(sendResumeParent(parent, executionId))
|
|
541
|
+
: Effect.void
|
|
538
542
|
if (
|
|
539
543
|
!instance.suspended &&
|
|
540
544
|
!instance.abandoned &&
|
|
541
545
|
!(suspendOnFailure && exit._tag === "Failure")
|
|
542
546
|
) {
|
|
543
|
-
const resumeParent = parent && !parent.detached
|
|
544
|
-
? ensureSuccess(sendResumeParent(parent, executionId))
|
|
545
|
-
: Effect.void
|
|
546
547
|
if (exit._tag === "Success" || !Cause.hasInterruptsOnly(exit.cause)) {
|
|
547
548
|
return resumeParent
|
|
548
549
|
}
|
|
@@ -568,9 +569,13 @@ export const make = Effect.gen(function*() {
|
|
|
568
569
|
}
|
|
569
570
|
instance.suspended = false
|
|
570
571
|
instance.interrupted = true
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
572
|
+
// The interrupt completes the run, so wake the parent
|
|
573
|
+
// to observe it.
|
|
574
|
+
return Effect.ignore(clearClock({ workflow, executionId })).pipe(
|
|
575
|
+
Effect.andThen(resumeParent),
|
|
576
|
+
Effect.andThen(
|
|
577
|
+
Effect.withFiber<void>((fiber) => Effect.interruptible(Fiber.interrupt(fiber)))
|
|
578
|
+
)
|
|
574
579
|
)
|
|
575
580
|
}),
|
|
576
581
|
Effect.orDie
|
|
@@ -760,7 +765,7 @@ export const make = Effect.gen(function*() {
|
|
|
760
765
|
// A pending interrupt replays only to rebuild compensations, so it must
|
|
761
766
|
// not run new activities. Finalizers run once `interrupted` is set.
|
|
762
767
|
if (!instance.interrupted && (yield* interruptPending(instance))) {
|
|
763
|
-
const reply = yield*
|
|
768
|
+
const reply = yield* retryPersistence(requestReply({
|
|
764
769
|
workflow: instance.workflow,
|
|
765
770
|
entityType: `Workflow/${instance.workflow._tag}`,
|
|
766
771
|
executionId: instance.executionId,
|
|
@@ -777,7 +782,7 @@ export const make = Effect.gen(function*() {
|
|
|
777
782
|
if (!activities.has(activityId)) {
|
|
778
783
|
activities.set(activityId, { activity, context: services })
|
|
779
784
|
}
|
|
780
|
-
const result = yield*
|
|
785
|
+
const result = yield* retryPersistence(
|
|
781
786
|
client.activity({
|
|
782
787
|
name: activity.name,
|
|
783
788
|
attempt,
|
|
@@ -917,6 +922,15 @@ const retryPolicy = Schedule.min([
|
|
|
917
922
|
Schedule.spaced("1 minute")
|
|
918
923
|
])
|
|
919
924
|
|
|
925
|
+
// A transient storage failure must not become the workflow result. Requests
|
|
926
|
+
// are saved under a primary key, so a retry after a lost acknowledgement is
|
|
927
|
+
// deduplicated instead of running the activity again.
|
|
928
|
+
const retryPersistence = <A, E extends { readonly _tag: string }, R>(effect: Effect.Effect<A, E, R>) =>
|
|
929
|
+
effect.pipe(
|
|
930
|
+
Effect.retry({ schedule: retryPolicy, while: (e) => e._tag === "PersistenceError" }),
|
|
931
|
+
Effect.orDie
|
|
932
|
+
)
|
|
933
|
+
|
|
920
934
|
// A cluster abandonment means this owner can no longer complete the effect, so
|
|
921
935
|
// retrying it would only wait on a retired run. Re-raise it as an annotated
|
|
922
936
|
// interrupt, so the activation can unwind and its shard can be released.
|
package/src/cluster/Reply.ts
CHANGED
|
@@ -220,7 +220,7 @@ export class Chunk<R extends Rpc.Any> extends Data.TaggedClass("Chunk")<{
|
|
|
220
220
|
* @stability unstable
|
|
221
221
|
* @since 4.0.0
|
|
222
222
|
*/
|
|
223
|
-
static readonly Any = Schema.declare((u): u is Chunk<
|
|
223
|
+
static readonly Any = Schema.declare((u): u is Chunk<Rpc.Any> => isReply(u) && u._tag === "Chunk")
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
226
|
* Transformation between encoded chunk records and `Chunk` instances.
|
package/src/cluster/Sharding.ts
CHANGED
|
@@ -1697,7 +1697,10 @@ const make = Effect.gen(function*() {
|
|
|
1697
1697
|
}
|
|
1698
1698
|
clientRequests.set(id, entry)
|
|
1699
1699
|
respond = makeClientRespond(entry, client.write)
|
|
1700
|
-
|
|
1700
|
+
// A failed send leaves no reply or interrupt to release the entry.
|
|
1701
|
+
// Only drop the local bookkeeping, as the request may have been
|
|
1702
|
+
// persisted.
|
|
1703
|
+
return Effect.tapError(sendOutgoing(message, false), () => Effect.sync(() => clientRequests.delete(id)))
|
|
1701
1704
|
}
|
|
1702
1705
|
case "Ack": {
|
|
1703
1706
|
const requestId = Snowflake.Snowflake(options.message.requestId)
|
|
@@ -369,8 +369,11 @@ export const make = Effect.fnUntraced(function*<
|
|
|
369
369
|
)
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
|
+
// A failed save can abort the request transaction, so a retry
|
|
373
|
+
// could store a success outside it. Save once and let the
|
|
374
|
+
// failed transaction replay the request.
|
|
372
375
|
const write = retryRespond(
|
|
373
|
-
4,
|
|
376
|
+
persisted && request.transaction && Exit.isSuccess(response.exit) ? 0 : 4,
|
|
374
377
|
Effect.suspend(() =>
|
|
375
378
|
request.message.respond(
|
|
376
379
|
new Reply.WithExit({
|
|
@@ -407,8 +410,25 @@ export const make = Effect.fnUntraced(function*<
|
|
|
407
410
|
}
|
|
408
411
|
removeActiveRequest(Snowflake.Snowflake(response.requestId))
|
|
409
412
|
})
|
|
413
|
+
// Let callers read the saved reply instead of waiting on a
|
|
414
|
+
// handler that will never be resumed.
|
|
415
|
+
const completeFromStorage = Effect.andThen(
|
|
416
|
+
complete,
|
|
417
|
+
options.storage.unregisterReplyHandler(request.message.envelope.requestId)
|
|
418
|
+
)
|
|
410
419
|
const respond = Effect.andThen(save, complete).pipe(
|
|
411
420
|
Effect.catchTag("EntityNotAssignedToRunner", () => abandonFenced),
|
|
421
|
+
// A save can commit and still fail, e.g. when the COMMIT
|
|
422
|
+
// acknowledgement is lost. Volatile requests skip the lookup:
|
|
423
|
+
// their replies are never stored, and a storage read could
|
|
424
|
+
// stall a custom responder's failure.
|
|
425
|
+
Effect.catchTag("PersistenceError", (error) =>
|
|
426
|
+
persisted
|
|
427
|
+
? Effect.flatMap(
|
|
428
|
+
hasStoredExit(request.message.envelope.requestId),
|
|
429
|
+
(committed) => committed ? completeFromStorage : Effect.fail(error)
|
|
430
|
+
)
|
|
431
|
+
: Effect.fail(error)),
|
|
412
432
|
Effect.orDie
|
|
413
433
|
)
|
|
414
434
|
const transaction = request.transaction
|
|
@@ -427,16 +447,10 @@ export const make = Effect.fnUntraced(function*<
|
|
|
427
447
|
// A rejected save failed the transaction, rolling back the
|
|
428
448
|
// handler's writes; the request is left for the owner.
|
|
429
449
|
if (fenced) return abandonFenced
|
|
450
|
+
// The commit can succeed despite the error.
|
|
430
451
|
return Effect.flatMap(
|
|
431
452
|
hasStoredExit(request.message.envelope.requestId),
|
|
432
|
-
(committed) =>
|
|
433
|
-
if (!committed) return restartFrom(outcome.cause)
|
|
434
|
-
// Commit succeeded despite the error; let callers read the saved reply.
|
|
435
|
-
return Effect.andThen(
|
|
436
|
-
complete,
|
|
437
|
-
options.storage.unregisterReplyHandler(request.message.envelope.requestId)
|
|
438
|
-
)
|
|
439
|
-
}
|
|
453
|
+
(committed) => committed ? completeFromStorage : restartFrom(outcome.cause)
|
|
440
454
|
)
|
|
441
455
|
}
|
|
442
456
|
return Effect.orDie(save)
|
|
@@ -977,7 +991,7 @@ const makeMessageDecode = <Rpcs extends Rpc.Any>(entityRpcs: Map<string, Rpcs>)
|
|
|
977
991
|
)
|
|
978
992
|
)
|
|
979
993
|
}
|
|
980
|
-
return decodeRequest(message, rpc) as Effect.Effect<
|
|
994
|
+
return decodeRequest(message, rpc) as any as Effect.Effect<
|
|
981
995
|
{
|
|
982
996
|
readonly _tag: "IncomingRequest"
|
|
983
997
|
readonly envelope: Envelope.Request.Any
|
package/src/crypto/Aead.ts
CHANGED
|
@@ -37,7 +37,7 @@ import * as Chacha from "./internal/chacha.ts"
|
|
|
37
37
|
import * as KeyModel from "./internal/key.ts"
|
|
38
38
|
import * as internal from "./internal/webCrypto.ts"
|
|
39
39
|
import * as SecureRandom from "./SecureRandom.ts"
|
|
40
|
-
import
|
|
40
|
+
import * as WebCrypto from "./WebCrypto.ts"
|
|
41
41
|
|
|
42
42
|
const TypeId = "~effect/crypto/Aead/Key" as const
|
|
43
43
|
|
|
@@ -374,7 +374,7 @@ export const layerWebCryptoWith = (options: {
|
|
|
374
374
|
Aead,
|
|
375
375
|
Effect.gen(function*() {
|
|
376
376
|
const random = yield* SecureRandom.SecureRandom
|
|
377
|
-
const crypto = yield* WebCrypto
|
|
377
|
+
const crypto = yield* WebCrypto.WebCrypto
|
|
378
378
|
const cryptoKeyFor = KeyModel.makeCache((subtle, bytes) =>
|
|
379
379
|
subtle.importKey("raw", bytes as BufferSource, "AES-GCM", false, ["encrypt", "decrypt"])
|
|
380
380
|
)
|
|
@@ -488,7 +488,7 @@ export const key = <A extends Algorithm>(
|
|
|
488
488
|
*/
|
|
489
489
|
export const fromCryptoKey = <A extends AesGcm>(
|
|
490
490
|
algorithm: A,
|
|
491
|
-
cryptoKey: CryptoKey
|
|
491
|
+
cryptoKey: WebCrypto.CryptoKey
|
|
492
492
|
): Effect.Effect<Key<A>, CryptoError.CryptoError> =>
|
|
493
493
|
algorithm.implementation !== undefined
|
|
494
494
|
? internal.invalidKey("Aead", "fromCryptoKey", `${algorithm.name} keys cannot be CryptoKeys`)
|