@yolk-sdk/knowledge 0.0.1-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -0
  3. package/dist/agent.d.mts +26 -0
  4. package/dist/agent.d.mts.map +1 -0
  5. package/dist/agent.mjs +45 -0
  6. package/dist/agent.mjs.map +1 -0
  7. package/dist/artifacts.d.mts +37 -0
  8. package/dist/artifacts.d.mts.map +1 -0
  9. package/dist/artifacts.mjs +28 -0
  10. package/dist/artifacts.mjs.map +1 -0
  11. package/dist/chunking.d.mts +38 -0
  12. package/dist/chunking.d.mts.map +1 -0
  13. package/dist/chunking.mjs +93 -0
  14. package/dist/chunking.mjs.map +1 -0
  15. package/dist/context.d.mts +16 -0
  16. package/dist/context.d.mts.map +1 -0
  17. package/dist/context.mjs +25 -0
  18. package/dist/context.mjs.map +1 -0
  19. package/dist/documents.d.mts +105 -0
  20. package/dist/documents.d.mts.map +1 -0
  21. package/dist/documents.mjs +93 -0
  22. package/dist/documents.mjs.map +1 -0
  23. package/dist/embeddings.d.mts +14 -0
  24. package/dist/embeddings.d.mts.map +1 -0
  25. package/dist/embeddings.mjs +8 -0
  26. package/dist/embeddings.mjs.map +1 -0
  27. package/dist/errors.d.mts +72 -0
  28. package/dist/errors.d.mts.map +1 -0
  29. package/dist/errors.mjs +26 -0
  30. package/dist/errors.mjs.map +1 -0
  31. package/dist/extraction.d.mts +19 -0
  32. package/dist/extraction.d.mts.map +1 -0
  33. package/dist/extraction.mjs +7 -0
  34. package/dist/extraction.mjs.map +1 -0
  35. package/dist/index.d.mts +5 -0
  36. package/dist/index.mjs +5 -0
  37. package/dist/ingestion.d.mts +48 -0
  38. package/dist/ingestion.d.mts.map +1 -0
  39. package/dist/ingestion.mjs +111 -0
  40. package/dist/ingestion.mjs.map +1 -0
  41. package/dist/links.d.mts +17 -0
  42. package/dist/links.d.mts.map +1 -0
  43. package/dist/links.mjs +24 -0
  44. package/dist/links.mjs.map +1 -0
  45. package/dist/provenance.d.mts +20 -0
  46. package/dist/provenance.d.mts.map +1 -0
  47. package/dist/provenance.mjs +26 -0
  48. package/dist/provenance.mjs.map +1 -0
  49. package/dist/records.d.mts +59 -0
  50. package/dist/records.d.mts.map +1 -0
  51. package/dist/records.mjs +64 -0
  52. package/dist/records.mjs.map +1 -0
  53. package/dist/representations.d.mts +36 -0
  54. package/dist/representations.d.mts.map +1 -0
  55. package/dist/representations.mjs +44 -0
  56. package/dist/representations.mjs.map +1 -0
  57. package/dist/search-store.d.mts +77 -0
  58. package/dist/search-store.d.mts.map +1 -0
  59. package/dist/search-store.mjs +7 -0
  60. package/dist/search-store.mjs.map +1 -0
  61. package/dist/search.d.mts +49 -0
  62. package/dist/search.d.mts.map +1 -0
  63. package/dist/search.mjs +179 -0
  64. package/dist/search.mjs.map +1 -0
  65. package/dist/store.d.mts +44 -0
  66. package/dist/store.d.mts.map +1 -0
  67. package/dist/store.mjs +7 -0
  68. package/dist/store.mjs.map +1 -0
  69. package/dist/summarization.d.mts +23 -0
  70. package/dist/summarization.d.mts.map +1 -0
  71. package/dist/summarization.mjs +8 -0
  72. package/dist/summarization.mjs.map +1 -0
  73. package/dist/vector-store.d.mts +3 -0
  74. package/dist/vector-store.mjs +2 -0
  75. package/package.json +147 -0
  76. package/src/agent.ts +96 -0
  77. package/src/artifacts.ts +48 -0
  78. package/src/chunking.ts +175 -0
  79. package/src/context.ts +42 -0
  80. package/src/documents.ts +109 -0
  81. package/src/embeddings.ts +18 -0
  82. package/src/errors.ts +63 -0
  83. package/src/extraction.ts +21 -0
  84. package/src/index.ts +4 -0
  85. package/src/ingestion.ts +157 -0
  86. package/src/links.ts +23 -0
  87. package/src/provenance.ts +25 -0
  88. package/src/records.ts +76 -0
  89. package/src/representations.ts +51 -0
  90. package/src/search-store.ts +98 -0
  91. package/src/search.ts +270 -0
  92. package/src/store.ts +53 -0
  93. package/src/summarization.ts +28 -0
  94. package/src/vector-store.ts +6 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.mts","names":[],"sources":["../src/store.ts"],"mappings":";;;;;;;;;KAcY,uBAAA;EAAA,SACD,KAAA,EAAO,cAAc;EAAA,SACrB,EAAA;AAAA;AAAA,KAGC,yBAAA;EAAA,SACD,KAAA,EAAO,cAAc;EAAA,SACrB,KAAA;AAAA;AAAA,KAGC,0BAAA;EAAA,SACD,OAAA,EAAS,aAAa,CAAC,eAAA;AAAA;AAAA,KAGtB,wBAAA;EAAA,SACD,KAAA,EAAO,cAAc;EAAA,SACrB,KAAA;AAAA;AAAA,KAGC,yBAAA;EAAA,SACD,OAAA,EAAS,aAAA,CAAc,eAAA;EAAA,SACvB,eAAA,EAAiB,aAAA,CAAc,uBAAA;AAAA;AAAA,KAG9B,iBAAA;EAAA,SACD,YAAA,GAAe,KAAA,EAAO,0BAAA,KAA+B,MAAA,CAAO,MAAA,CAAO,eAAA,EAAiB,mBAAA;EAAA,SACpF,YAAA,GAAe,KAAA,EAAO,0BAAA,KAA+B,MAAA,CAAO,MAAA,CAAO,eAAA,EAAiB,mBAAA;EAAA,SACpF,SAAA,GAAY,KAAA,EAAO,uBAAA,KAA4B,MAAA,CAAO,MAAA,CAAO,eAAA,EAAiB,mBAAA;EAAA,SAC9E,WAAA,GAAc,KAAA,EAAO,yBAAA,KAA8B,MAAA,CAAO,MAAA,CAAO,0BAAA,EAA4B,mBAAA;EAAA,SAC7F,UAAA,GAAa,KAAA,EAAO,wBAAA,KAA6B,MAAA,CAAO,MAAA,CAAO,yBAAA,EAA2B,mBAAA;EAAA,SAC1F,YAAA,GAAe,KAAA,EAAO,uBAAA,KAA4B,MAAA,CAAO,MAAA,OAAa,mBAAA;EAAA,SACtE,aAAA,GAAgB,KAAA,EAAO,uBAAA,KAA4B,MAAA,CAAO,MAAA,CAAO,aAAA,CAAc,iBAAA,GAAoB,mBAAA;EAAA,SACnG,cAAA,GAAiB,KAAA,EAAO,uBAAA,KAA4B,MAAA,CAAO,MAAA,CAAO,aAAA,CAAc,mBAAA,GAAsB,mBAAA;EAAA,SACtG,SAAA,GAAY,KAAA,EAAO,uBAAA,KAA4B,MAAA,CAAO,MAAA,CAAO,aAAA,CAAc,aAAA,GAAgB,mBAAA;AAAA;AAAA,cACrG,mBAAA;cAEY,cAAA,SAAuB,mBAEnC"}
package/dist/store.mjs ADDED
@@ -0,0 +1,7 @@
1
+ import { Context } from "effect";
2
+ //#region src/store.ts
3
+ var KnowledgeStore = class extends Context.Service()("@yolk-sdk/knowledge/KnowledgeStore") {};
4
+ //#endregion
5
+ export { KnowledgeStore };
6
+
7
+ //# sourceMappingURL=store.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import { Context } from 'effect'\nimport type { Effect } from 'effect'\nimport type { KnowledgeArtifact } from './artifacts.ts'\nimport type { KnowledgeStoreError } from './errors.ts'\nimport type { KnowledgeLink } from './links.ts'\nimport type {\n CreateKnowledgeRecordInput,\n KnowledgeRecord,\n KnowledgeScope,\n UpdateKnowledgeRecordInput\n} from './records.ts'\nimport type { KnowledgeProvenance } from './provenance.ts'\nimport type { KnowledgeRepresentation } from './representations.ts'\n\nexport type GetKnowledgeRecordInput = {\n readonly scope: KnowledgeScope\n readonly id: string\n}\n\nexport type ListKnowledgeRecordsInput = {\n readonly scope: KnowledgeScope\n readonly limit: number\n}\n\nexport type ListKnowledgeRecordsResult = {\n readonly records: ReadonlyArray<KnowledgeRecord>\n}\n\nexport type ListPinnedKnowledgeInput = {\n readonly scope: KnowledgeScope\n readonly limit: number\n}\n\nexport type ListPinnedKnowledgeResult = {\n readonly records: ReadonlyArray<KnowledgeRecord>\n readonly representations: ReadonlyArray<KnowledgeRepresentation>\n}\n\nexport type KnowledgeStoreApi = {\n readonly createRecord: (input: CreateKnowledgeRecordInput) => Effect.Effect<KnowledgeRecord, KnowledgeStoreError>\n readonly updateRecord: (input: UpdateKnowledgeRecordInput) => Effect.Effect<KnowledgeRecord, KnowledgeStoreError>\n readonly getRecord: (input: GetKnowledgeRecordInput) => Effect.Effect<KnowledgeRecord, KnowledgeStoreError>\n readonly listRecords: (input: ListKnowledgeRecordsInput) => Effect.Effect<ListKnowledgeRecordsResult, KnowledgeStoreError>\n readonly listPinned: (input: ListPinnedKnowledgeInput) => Effect.Effect<ListPinnedKnowledgeResult, KnowledgeStoreError>\n readonly deleteRecord: (input: GetKnowledgeRecordInput) => Effect.Effect<void, KnowledgeStoreError>\n readonly listArtifacts: (input: GetKnowledgeRecordInput) => Effect.Effect<ReadonlyArray<KnowledgeArtifact>, KnowledgeStoreError>\n readonly listProvenance: (input: GetKnowledgeRecordInput) => Effect.Effect<ReadonlyArray<KnowledgeProvenance>, KnowledgeStoreError>\n readonly listLinks: (input: GetKnowledgeRecordInput) => Effect.Effect<ReadonlyArray<KnowledgeLink>, KnowledgeStoreError>\n}\n\nexport class KnowledgeStore extends Context.Service<KnowledgeStore, KnowledgeStoreApi>()(\n '@yolk-sdk/knowledge/KnowledgeStore'\n) {}\n"],"mappings":";;AAkDA,IAAa,iBAAb,cAAoC,QAAQ,QAA2C,EACrF,oCACF,EAAE,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { KnowledgeMetadata } from "./documents.mjs";
2
+ import { KnowledgeSummarizationError } from "./errors.mjs";
3
+ import { Context, Effect, Layer } from "effect";
4
+
5
+ //#region src/summarization.d.ts
6
+ type KnowledgeDocumentSummary = {
7
+ readonly title?: string;
8
+ readonly summary?: string;
9
+ };
10
+ type SummarizeKnowledgeDocumentInput = {
11
+ readonly content: string;
12
+ readonly sourceTitle?: string;
13
+ readonly metadata?: KnowledgeMetadata;
14
+ };
15
+ type KnowledgeSummarizerApi = {
16
+ readonly summarize: (input: SummarizeKnowledgeDocumentInput) => Effect.Effect<KnowledgeDocumentSummary, KnowledgeSummarizationError>;
17
+ };
18
+ declare const KnowledgeSummarizer_base: Context.ServiceClass<KnowledgeSummarizer, "@yolk-sdk/knowledge/KnowledgeSummarizer", KnowledgeSummarizerApi>;
19
+ declare class KnowledgeSummarizer extends KnowledgeSummarizer_base {}
20
+ declare const NoopKnowledgeSummarizerLive: Layer.Layer<KnowledgeSummarizer, never, never>;
21
+ //#endregion
22
+ export { KnowledgeDocumentSummary, KnowledgeSummarizer, KnowledgeSummarizerApi, NoopKnowledgeSummarizerLive, SummarizeKnowledgeDocumentInput };
23
+ //# sourceMappingURL=summarization.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarization.d.mts","names":[],"sources":["../src/summarization.ts"],"mappings":";;;;;KAIY,wBAAA;EAAA,SACD,KAAA;EAAA,SACA,OAAO;AAAA;AAAA,KAGN,+BAAA;EAAA,SACD,OAAA;EAAA,SACA,WAAA;EAAA,SACA,QAAA,GAAW,iBAAiB;AAAA;AAAA,KAG3B,sBAAA;EAAA,SACD,SAAA,GACP,KAAA,EAAO,+BAAA,KACJ,MAAA,CAAO,MAAA,CAAO,wBAAA,EAA0B,2BAAA;AAAA;AAAA,cAC9C,wBAAA;cAEY,mBAAA,SAA4B,wBAExC;AAAA,cAEY,2BAAA,EAA2B,KAAA,CAAA,KAAA,CAAA,mBAAA"}
@@ -0,0 +1,8 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ //#region src/summarization.ts
3
+ var KnowledgeSummarizer = class extends Context.Service()("@yolk-sdk/knowledge/KnowledgeSummarizer") {};
4
+ const NoopKnowledgeSummarizerLive = Layer.succeed(KnowledgeSummarizer, { summarize: (input) => Effect.succeed({ title: input.sourceTitle }) });
5
+ //#endregion
6
+ export { KnowledgeSummarizer, NoopKnowledgeSummarizerLive };
7
+
8
+ //# sourceMappingURL=summarization.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarization.mjs","names":[],"sources":["../src/summarization.ts"],"sourcesContent":["import { Context, Effect, Layer } from 'effect'\nimport type { KnowledgeMetadata } from './documents.ts'\nimport type { KnowledgeSummarizationError } from './errors.ts'\n\nexport type KnowledgeDocumentSummary = {\n readonly title?: string\n readonly summary?: string\n}\n\nexport type SummarizeKnowledgeDocumentInput = {\n readonly content: string\n readonly sourceTitle?: string\n readonly metadata?: KnowledgeMetadata\n}\n\nexport type KnowledgeSummarizerApi = {\n readonly summarize: (\n input: SummarizeKnowledgeDocumentInput\n ) => Effect.Effect<KnowledgeDocumentSummary, KnowledgeSummarizationError>\n}\n\nexport class KnowledgeSummarizer extends Context.Service<KnowledgeSummarizer, KnowledgeSummarizerApi>()(\n '@yolk-sdk/knowledge/KnowledgeSummarizer'\n) {}\n\nexport const NoopKnowledgeSummarizerLive = Layer.succeed(KnowledgeSummarizer, {\n summarize: input => Effect.succeed({ title: input.sourceTitle })\n})\n"],"mappings":";;AAqBA,IAAa,sBAAb,cAAyC,QAAQ,QAAqD,EACpG,yCACF,EAAE,CAAC;AAEH,MAAa,8BAA8B,MAAM,QAAQ,qBAAqB,EAC5E,YAAW,UAAS,OAAO,QAAQ,EAAE,OAAO,MAAM,YAAY,CAAC,EACjE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { SearchIndexStoreError } from "./errors.mjs";
2
+ import { KnowledgeChunkSearchInput, KnowledgeChunkSearchResult, ReplaceKnowledgeDocumentChunksInput } from "./search-store.mjs";
3
+ export { type ReplaceKnowledgeDocumentChunksInput as VectorRecord, type KnowledgeChunkSearchInput as VectorSearchQuery, type KnowledgeChunkSearchResult as VectorSearchResult, SearchIndexStoreError as VectorStoreError };
@@ -0,0 +1,2 @@
1
+ import { SearchIndexStoreError } from "./errors.mjs";
2
+ export { SearchIndexStoreError as VectorStoreError };
package/package.json ADDED
@@ -0,0 +1,147 @@
1
+ {
2
+ "name": "@yolk-sdk/knowledge",
3
+ "version": "0.0.1-canary.0",
4
+ "description": "Domain-free knowledge record, artifact, provenance, and context contracts for Yolk agents.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/magoz/yolk-sdk.git",
11
+ "directory": "packages/knowledge"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/magoz/yolk-sdk/issues"
15
+ },
16
+ "homepage": "https://github.com/magoz/yolk-sdk#readme",
17
+ "keywords": [
18
+ "knowledge",
19
+ "agents",
20
+ "context",
21
+ "provenance",
22
+ "effect"
23
+ ],
24
+ "engines": {
25
+ "node": ">=22"
26
+ },
27
+ "exports": {
28
+ "./package.json": "./package.json",
29
+ ".": {
30
+ "types": "./dist/index.d.mts",
31
+ "import": "./dist/index.mjs",
32
+ "default": "./dist/index.mjs"
33
+ },
34
+ "./agent": {
35
+ "types": "./dist/agent.d.mts",
36
+ "import": "./dist/agent.mjs",
37
+ "default": "./dist/agent.mjs"
38
+ },
39
+ "./artifacts": {
40
+ "types": "./dist/artifacts.d.mts",
41
+ "import": "./dist/artifacts.mjs",
42
+ "default": "./dist/artifacts.mjs"
43
+ },
44
+ "./context": {
45
+ "types": "./dist/context.d.mts",
46
+ "import": "./dist/context.mjs",
47
+ "default": "./dist/context.mjs"
48
+ },
49
+ "./chunking": {
50
+ "types": "./dist/chunking.d.mts",
51
+ "import": "./dist/chunking.mjs",
52
+ "default": "./dist/chunking.mjs"
53
+ },
54
+ "./embeddings": {
55
+ "types": "./dist/embeddings.d.mts",
56
+ "import": "./dist/embeddings.mjs",
57
+ "default": "./dist/embeddings.mjs"
58
+ },
59
+ "./errors": {
60
+ "types": "./dist/errors.d.mts",
61
+ "import": "./dist/errors.mjs",
62
+ "default": "./dist/errors.mjs"
63
+ },
64
+ "./extraction": {
65
+ "types": "./dist/extraction.d.mts",
66
+ "import": "./dist/extraction.mjs",
67
+ "default": "./dist/extraction.mjs"
68
+ },
69
+ "./ingestion": {
70
+ "types": "./dist/ingestion.d.mts",
71
+ "import": "./dist/ingestion.mjs",
72
+ "default": "./dist/ingestion.mjs"
73
+ },
74
+ "./documents": {
75
+ "types": "./dist/documents.d.mts",
76
+ "import": "./dist/documents.mjs",
77
+ "default": "./dist/documents.mjs"
78
+ },
79
+ "./search-store": {
80
+ "types": "./dist/search-store.d.mts",
81
+ "import": "./dist/search-store.mjs",
82
+ "default": "./dist/search-store.mjs"
83
+ },
84
+ "./links": {
85
+ "types": "./dist/links.d.mts",
86
+ "import": "./dist/links.mjs",
87
+ "default": "./dist/links.mjs"
88
+ },
89
+ "./records": {
90
+ "types": "./dist/records.d.mts",
91
+ "import": "./dist/records.mjs",
92
+ "default": "./dist/records.mjs"
93
+ },
94
+ "./provenance": {
95
+ "types": "./dist/provenance.d.mts",
96
+ "import": "./dist/provenance.mjs",
97
+ "default": "./dist/provenance.mjs"
98
+ },
99
+ "./representations": {
100
+ "types": "./dist/representations.d.mts",
101
+ "import": "./dist/representations.mjs",
102
+ "default": "./dist/representations.mjs"
103
+ },
104
+ "./search": {
105
+ "types": "./dist/search.d.mts",
106
+ "import": "./dist/search.mjs",
107
+ "default": "./dist/search.mjs"
108
+ },
109
+ "./store": {
110
+ "types": "./dist/store.d.mts",
111
+ "import": "./dist/store.mjs",
112
+ "default": "./dist/store.mjs"
113
+ },
114
+ "./summarization": {
115
+ "types": "./dist/summarization.d.mts",
116
+ "import": "./dist/summarization.mjs",
117
+ "default": "./dist/summarization.mjs"
118
+ },
119
+ "./vector-store": {
120
+ "types": "./dist/vector-store.d.mts",
121
+ "import": "./dist/vector-store.mjs",
122
+ "default": "./dist/vector-store.mjs"
123
+ }
124
+ },
125
+ "files": [
126
+ "src/**/*.ts",
127
+ "!src/**/*.test.ts",
128
+ "!src/**/*.test.tsx",
129
+ "dist/**/*",
130
+ "README.md"
131
+ ],
132
+ "publishConfig": {
133
+ "access": "public",
134
+ "provenance": true
135
+ },
136
+ "dependencies": {
137
+ "effect": "4.0.0-beta.65",
138
+ "gpt-tokenizer": "^3.4.0",
139
+ "@yolk-sdk/agent": "^0.0.1-canary.0"
140
+ },
141
+ "scripts": {
142
+ "build": "tsdown",
143
+ "check": "tsc -p tsconfig.json --noEmit",
144
+ "test": "vitest run --passWithNoTests",
145
+ "test:run": "vitest run --passWithNoTests"
146
+ }
147
+ }
package/src/agent.ts ADDED
@@ -0,0 +1,96 @@
1
+ import { Effect } from 'effect'
2
+ import * as Schema from 'effect/Schema'
3
+ import { ToolError } from '@yolk-sdk/agent/loop'
4
+ import { ToolResult } from '@yolk-sdk/agent/protocol'
5
+ import { makeTool, type ToolRegistration } from '@yolk-sdk/agent/tools'
6
+ import type { KnowledgeScope } from './records.ts'
7
+ import type { KnowledgeSearchScope } from './documents.ts'
8
+ import type { KnowledgeSearcher } from './search.ts'
9
+ import { packKnowledgeSearchContext } from './search.ts'
10
+
11
+ export type ResolveKnowledgeScope<Context> = (context: Context) => KnowledgeScope
12
+
13
+ export type KnowledgeAgentContextOptions = {
14
+ readonly maxPinnedContextCharacters: number
15
+ }
16
+
17
+ export const defaultKnowledgeAgentContextOptions: KnowledgeAgentContextOptions = {
18
+ maxPinnedContextCharacters: 6000
19
+ }
20
+
21
+ const KnowledgeSearchToolParams = Schema.Struct({
22
+ query: Schema.Trimmed.pipe(
23
+ Schema.check(Schema.isNonEmpty()),
24
+ Schema.annotate({ description: 'Search query for the configured knowledge search.' })
25
+ )
26
+ })
27
+
28
+ const isToolError = Schema.is(ToolError)
29
+
30
+ export type KnowledgeSearchToolScopeResolver<Context> =
31
+ | KnowledgeSearchScope
32
+ | ((context: Context) => Effect.Effect<KnowledgeSearchScope, ToolError>)
33
+
34
+ export type MakeKnowledgeSearchToolOptions<Context> = {
35
+ readonly scope: KnowledgeSearchToolScopeResolver<Context>
36
+ readonly name?: string
37
+ readonly description?: string
38
+ readonly limit?: number
39
+ readonly minScore?: number
40
+ readonly contextChunks?: number
41
+ }
42
+
43
+ const resolveScope = <Context>(resolver: KnowledgeSearchToolScopeResolver<Context>, context: Context) => {
44
+ if (typeof resolver === 'function') {
45
+ return resolver(context)
46
+ }
47
+
48
+ return Effect.succeed(resolver)
49
+ }
50
+
51
+ export const makeKnowledgeSearchTool = <Context>(
52
+ searcher: KnowledgeSearcher,
53
+ options: MakeKnowledgeSearchToolOptions<Context>
54
+ ): ToolRegistration<Context> => {
55
+ const name = options.name ?? 'search_knowledge'
56
+
57
+ return makeTool({
58
+ name,
59
+ description: options.description ?? 'Search the configured knowledge search.',
60
+ parameters: KnowledgeSearchToolParams,
61
+ access: 'read',
62
+ invalidParamsMessage: error => error instanceof Error ? error.message : String(error),
63
+ execute: input =>
64
+ resolveScope(options.scope, input.context).pipe(
65
+ Effect.flatMap(scope =>
66
+ searcher.search({
67
+ scope,
68
+ query: input.params.query,
69
+ limit: options.limit,
70
+ minScore: options.minScore,
71
+ contextChunks: options.contextChunks
72
+ })
73
+ ),
74
+ Effect.map(results => packKnowledgeSearchContext(input.params.query, results)),
75
+ Effect.map(
76
+ context =>
77
+ ToolResult.make({
78
+ toolCallId: input.call.id,
79
+ content: context.text,
80
+ structuredContent: context
81
+ })
82
+ ),
83
+ Effect.mapError(error => {
84
+ if (isToolError(error)) {
85
+ return error
86
+ }
87
+
88
+ return new ToolError({
89
+ tool: input.call.name,
90
+ message: error.message,
91
+ cause: 'execution'
92
+ })
93
+ })
94
+ )
95
+ })
96
+ }
@@ -0,0 +1,48 @@
1
+ import { Context } from 'effect'
2
+ import type { Effect } from 'effect'
3
+ import * as Schema from 'effect/Schema'
4
+ import type { KnowledgeArtifactError } from './errors.ts'
5
+ import { KnowledgeMetadataSchema, NonEmptyTrimmedString, NonNegativeInteger } from './records.ts'
6
+
7
+ export const KnowledgeArtifactKindSchema = Schema.Literals([
8
+ 'original',
9
+ 'extracted_text',
10
+ 'thumbnail',
11
+ 'transcript',
12
+ 'caption',
13
+ 'structured'
14
+ ])
15
+ export type KnowledgeArtifactKind = Schema.Schema.Type<typeof KnowledgeArtifactKindSchema>
16
+
17
+ export const KnowledgeArtifactSchema = Schema.Struct({
18
+ id: NonEmptyTrimmedString,
19
+ recordId: NonEmptyTrimmedString,
20
+ kind: KnowledgeArtifactKindSchema,
21
+ storageKey: NonEmptyTrimmedString,
22
+ mediaType: Schema.optional(NonEmptyTrimmedString),
23
+ byteSize: Schema.optional(NonNegativeInteger),
24
+ checksum: Schema.optional(NonEmptyTrimmedString),
25
+ metadata: Schema.optional(KnowledgeMetadataSchema),
26
+ createdAt: Schema.DateTimeUtc
27
+ })
28
+ export type KnowledgeArtifact = Schema.Schema.Type<typeof KnowledgeArtifactSchema>
29
+
30
+ export type PutKnowledgeArtifactInput = {
31
+ readonly storageKey: string
32
+ readonly mediaType?: string
33
+ readonly bytes: Uint8Array
34
+ }
35
+
36
+ export type GetKnowledgeArtifactInput = {
37
+ readonly storageKey: string
38
+ }
39
+
40
+ export type KnowledgeArtifactStoreApi = {
41
+ readonly putArtifact: (input: PutKnowledgeArtifactInput) => Effect.Effect<void, KnowledgeArtifactError>
42
+ readonly getArtifact: (input: GetKnowledgeArtifactInput) => Effect.Effect<Uint8Array, KnowledgeArtifactError>
43
+ readonly deleteArtifact: (input: GetKnowledgeArtifactInput) => Effect.Effect<void, KnowledgeArtifactError>
44
+ }
45
+
46
+ export class KnowledgeArtifactStore extends Context.Service<KnowledgeArtifactStore, KnowledgeArtifactStoreApi>()(
47
+ '@yolk-sdk/knowledge/KnowledgeArtifactStore'
48
+ ) {}
@@ -0,0 +1,175 @@
1
+ import { Context, Effect, Layer } from 'effect'
2
+ import { countTokens, decode, encode } from 'gpt-tokenizer/encoding/o200k_base'
3
+ import type { KnowledgeChunk, KnowledgeMetadata } from './documents.ts'
4
+ import { KnowledgeChunkingError } from './errors.ts'
5
+
6
+ const SENTENCE_PATTERN = /[^.!?]+[.!?]+(?:["')\]]+)?\s*|[^.!?]+$/g
7
+ const PARAGRAPH_BREAK_PATTERN = /(\n{2,})/
8
+ const WORD_PATTERN = /\S+\s*/g
9
+
10
+ export type ChunkKnowledgeDocumentInput = {
11
+ readonly collectionId: string
12
+ readonly documentId: string
13
+ readonly content: string
14
+ readonly maxTokens?: number
15
+ readonly metadata?: KnowledgeMetadata
16
+ }
17
+
18
+ export type KnowledgeChunkerApi = {
19
+ readonly chunk: (
20
+ input: ChunkKnowledgeDocumentInput
21
+ ) => Effect.Effect<ReadonlyArray<KnowledgeChunk>, KnowledgeChunkingError>
22
+ }
23
+
24
+ export class KnowledgeChunker extends Context.Service<KnowledgeChunker, KnowledgeChunkerApi>()(
25
+ '@yolk-sdk/knowledge/KnowledgeChunker'
26
+ ) {}
27
+
28
+ export const countKnowledgeChunkTokens = (text: string) => countTokens(text)
29
+
30
+ const sanitizeText = (text: string) =>
31
+ text
32
+ .replace(/\r\n/g, '\n')
33
+ .replace(/[\t ]+/g, ' ')
34
+ .replace(/\n{3,}/g, '\n\n')
35
+ .trim()
36
+
37
+ const splitSentences = (text: string) =>
38
+ text
39
+ .split(PARAGRAPH_BREAK_PATTERN)
40
+ .flatMap(part => {
41
+ if (part.length === 0) {
42
+ return []
43
+ }
44
+
45
+ if (/^\n{2,}$/.test(part)) {
46
+ return ['\n\n']
47
+ }
48
+
49
+ return part.match(SENTENCE_PATTERN) ?? [part]
50
+ })
51
+ .filter(unit => unit.length > 0)
52
+
53
+ const splitEncodedTokens = (text: string, maxTokens: number) => {
54
+ const tokens = encode(text)
55
+ return Array.from({ length: Math.ceil(tokens.length / maxTokens) }, (_, index) =>
56
+ decode(tokens.slice(index * maxTokens, index * maxTokens + maxTokens)).trim()
57
+ ).filter(chunk => chunk.length > 0)
58
+ }
59
+
60
+ const splitOversizedUnit = (text: string, maxTokens: number) => {
61
+ const words = text.match(WORD_PATTERN) ?? []
62
+ const chunks: string[] = []
63
+ let current = ''
64
+ let currentTokenCount = 0
65
+
66
+ for (const word of words) {
67
+ const wordTokenCount = countTokens(word)
68
+
69
+ if (wordTokenCount > maxTokens) {
70
+ const currentChunk = current.trim()
71
+ if (currentChunk.length > 0) {
72
+ chunks.push(currentChunk)
73
+ }
74
+
75
+ chunks.push(...splitEncodedTokens(word, maxTokens))
76
+ current = ''
77
+ currentTokenCount = 0
78
+ continue
79
+ }
80
+
81
+ if (currentTokenCount > 0 && currentTokenCount + wordTokenCount > maxTokens) {
82
+ const currentChunk = current.trim()
83
+ if (currentChunk.length > 0) {
84
+ chunks.push(currentChunk)
85
+ }
86
+
87
+ current = word
88
+ currentTokenCount = wordTokenCount
89
+ continue
90
+ }
91
+
92
+ current = `${current}${word}`
93
+ currentTokenCount += wordTokenCount
94
+ }
95
+
96
+ const finalChunk = current.trim()
97
+ if (finalChunk.length > 0) {
98
+ chunks.push(finalChunk)
99
+ }
100
+
101
+ return chunks
102
+ }
103
+
104
+ const splitUnitsBySize = (units: ReadonlyArray<string>, maxTokens: number) =>
105
+ units.flatMap(unit =>
106
+ countTokens(unit) <= maxTokens ? [unit] : splitOversizedUnit(unit, maxTokens)
107
+ )
108
+
109
+ const buildChunkContents = (units: ReadonlyArray<string>, maxTokens: number) => {
110
+ const chunks: string[] = []
111
+ let current = ''
112
+ let currentTokenCount = 0
113
+
114
+ const pushCurrent = () => {
115
+ const content = current.trim()
116
+ if (content.length > 0) {
117
+ chunks.push(content)
118
+ }
119
+ }
120
+
121
+ for (const unit of units) {
122
+ const unitTokenCount = countTokens(unit)
123
+
124
+ if (currentTokenCount > 0 && currentTokenCount + unitTokenCount > maxTokens) {
125
+ pushCurrent()
126
+ current = unit
127
+ currentTokenCount = unitTokenCount
128
+ continue
129
+ }
130
+
131
+ current = `${current}${unit}`
132
+ currentTokenCount += unitTokenCount
133
+ }
134
+
135
+ pushCurrent()
136
+ return chunks
137
+ }
138
+
139
+ export const chunkKnowledgeText = (input: ChunkKnowledgeDocumentInput, maxTokens: number) =>
140
+ Effect.gen(function* () {
141
+ if (!Number.isInteger(maxTokens) || maxTokens < 1) {
142
+ return yield* Effect.fail(
143
+ new KnowledgeChunkingError({ message: 'Chunk maxTokens must be a positive integer' })
144
+ )
145
+ }
146
+
147
+ const sanitized = sanitizeText(input.content)
148
+ if (sanitized.length === 0) {
149
+ return yield* Effect.fail(new KnowledgeChunkingError({ message: 'Cannot chunk empty content' }))
150
+ }
151
+
152
+ const units = splitUnitsBySize(splitSentences(sanitized), maxTokens)
153
+ const contents = buildChunkContents(units, maxTokens)
154
+
155
+ if (contents.length === 0) {
156
+ return yield* Effect.fail(new KnowledgeChunkingError({ message: 'No chunks produced' }))
157
+ }
158
+
159
+ return contents.map((content, position) => ({
160
+ id: `${input.documentId}:chunk:${position}`,
161
+ collectionId: input.collectionId,
162
+ documentId: input.documentId,
163
+ content,
164
+ position,
165
+ tokenCount: countTokens(content),
166
+ metadata: input.metadata
167
+ })) satisfies ReadonlyArray<KnowledgeChunk>
168
+ })
169
+
170
+ export const makeDefaultKnowledgeChunker = (input: { readonly maxTokens: number }): KnowledgeChunkerApi => ({
171
+ chunk: document => chunkKnowledgeText(document, document.maxTokens ?? input.maxTokens)
172
+ })
173
+
174
+ export const DefaultKnowledgeChunkerLive = (input: { readonly maxTokens: number } = { maxTokens: 512 }) =>
175
+ Layer.succeed(KnowledgeChunker, makeDefaultKnowledgeChunker(input))
package/src/context.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { Array as Arr } from 'effect'
2
+ import type { KnowledgeRecord } from './records.ts'
3
+ import type { KnowledgeRepresentation } from './representations.ts'
4
+
5
+ export type KnowledgeContextItem = {
6
+ readonly record: KnowledgeRecord
7
+ readonly representation?: KnowledgeRepresentation
8
+ }
9
+
10
+ export type BuildKnowledgeContextInput = {
11
+ readonly items: ReadonlyArray<KnowledgeContextItem>
12
+ readonly maxCharacters: number
13
+ }
14
+
15
+ const truncationMarker = '\n\n[truncated: pinned knowledge exceeded context budget]'
16
+
17
+ const recordHeader = (record: KnowledgeRecord) =>
18
+ `## ${record.title}\nrole: ${record.role}\nstatus: ${record.status}\ncontext: ${record.contextPolicy}`
19
+
20
+ const itemBody = (item: KnowledgeContextItem) => {
21
+ const representationText = item.representation?.summary ?? item.representation?.contentText
22
+ return [recordHeader(item.record), item.record.summary, representationText]
23
+ .filter(section => section !== undefined && section.trim().length > 0)
24
+ .join('\n')
25
+ }
26
+
27
+ export const buildKnowledgeContext = (input: BuildKnowledgeContextInput) => {
28
+ const sections = Arr.map(input.items, itemBody).filter(section => section.trim().length > 0)
29
+ const header = '# Pinned knowledge\nUse this durable user knowledge as high-priority context. Cite knowledge records when relevant.'
30
+ const body = sections.join('\n\n')
31
+ const context = body.length === 0 ? '' : `${header}\n\n${body}`
32
+
33
+ if (context.length <= input.maxCharacters) {
34
+ return context
35
+ }
36
+
37
+ if (input.maxCharacters <= truncationMarker.length) {
38
+ return context.slice(0, input.maxCharacters).trimEnd()
39
+ }
40
+
41
+ return `${context.slice(0, input.maxCharacters - truncationMarker.length).trimEnd()}${truncationMarker}`
42
+ }