@vezham/tamizhi-core 0.0.0-stage → 26.3.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/LICENSE +173 -0
- package/README.md +6 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/http.d.ts +9 -0
- package/dist/lib/http.d.ts.map +1 -0
- package/dist/lib/http.js +120 -0
- package/dist/lib/linked-abort.d.ts +6 -0
- package/dist/lib/linked-abort.d.ts.map +1 -0
- package/dist/lib/linked-abort.js +15 -0
- package/dist/lib/node.d.ts +5 -0
- package/dist/lib/node.d.ts.map +1 -0
- package/dist/lib/node.js +98 -0
- package/dist/lib/plugins.d.ts +7 -0
- package/dist/lib/plugins.d.ts.map +1 -0
- package/dist/lib/plugins.js +48 -0
- package/dist/lib/queue.d.ts +7 -0
- package/dist/lib/queue.d.ts.map +1 -0
- package/dist/lib/queue.js +38 -0
- package/dist/lib/registry.d.ts +3 -0
- package/dist/lib/registry.d.ts.map +1 -0
- package/dist/lib/registry.js +20 -0
- package/dist/lib/runtime.d.ts +22 -0
- package/dist/lib/runtime.d.ts.map +1 -0
- package/dist/lib/runtime.js +248 -0
- package/dist/lib/tanstack-provider.d.ts +22 -0
- package/dist/lib/tanstack-provider.d.ts.map +1 -0
- package/dist/lib/tanstack-provider.js +50 -0
- package/dist/lib/types.d.ts +69 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/package.json +40 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
|
|
3
|
+
Business Source License 1.1
|
|
4
|
+
|
|
5
|
+
License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
|
|
6
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
|
|
12
|
+
Licensor: Vezham Technologies Private Limited
|
|
13
|
+
|
|
14
|
+
Licensed Work: The source code and packages in this repository (“the Licensed Work”).
|
|
15
|
+
The Licensed Work is (c) 2025–present Vezham Technologies Private Limited
|
|
16
|
+
|
|
17
|
+
Release Tag: The applicable release tag or commit identifier (for example, “v1.0.0”)
|
|
18
|
+
published by the Licensor for this version of the Licensed Work.
|
|
19
|
+
Users must cross-check the tag or commit identifier to verify
|
|
20
|
+
the release version and applicable Change Date.
|
|
21
|
+
|
|
22
|
+
Additional Use Grant: 1. You may make non-production use of the Licensed Work.
|
|
23
|
+
|
|
24
|
+
2. You may make production use of the Licensed Work, provided
|
|
25
|
+
your use does not include offering the Licensed Work, or any
|
|
26
|
+
derivative thereof, to third parties on a hosted, embedded,
|
|
27
|
+
or managed basis in order to compete with the Licensor’s
|
|
28
|
+
commercial offerings based on the Licensed Work, or offerings
|
|
29
|
+
that provide substantially similar functionality.
|
|
30
|
+
|
|
31
|
+
For purposes of this license:
|
|
32
|
+
|
|
33
|
+
A “competitive offering” is a Product that is offered to third
|
|
34
|
+
parties on a paid basis, including through paid support,
|
|
35
|
+
consulting, managed service arrangements, or similar commercial
|
|
36
|
+
relationships, that significantly overlaps with the capabilities
|
|
37
|
+
or functionality of the Licensor’s commercial offerings based on
|
|
38
|
+
the Licensed Work.
|
|
39
|
+
|
|
40
|
+
If your Product is not a competitive offering when you first
|
|
41
|
+
make it generally available, it will not become a competitive
|
|
42
|
+
offering solely because the Licensor releases a new version of the
|
|
43
|
+
Licensed Work with additional features or capabilities. Products
|
|
44
|
+
that are not provided on a paid basis are not competitive offerings.
|
|
45
|
+
|
|
46
|
+
“Product” means any software or service that is (a) offered to end
|
|
47
|
+
users for deployment in their own environments (self-managed), or
|
|
48
|
+
(b) offered as a hosted, cloud, or managed service by you or on
|
|
49
|
+
your behalf.
|
|
50
|
+
|
|
51
|
+
“Embedded” means including the source code or executable code of
|
|
52
|
+
the Licensed Work in a competitive offering. “Embedded” also means
|
|
53
|
+
packaging or distributing the competitive offering in a way that
|
|
54
|
+
requires the Licensed Work to be accessed, invoked, or downloaded
|
|
55
|
+
for the competitive offering to function.
|
|
56
|
+
|
|
57
|
+
Hosting or using the Licensed Work internally within your own
|
|
58
|
+
organization, including your affiliates under common control,
|
|
59
|
+
is not considered a competitive offering.
|
|
60
|
+
|
|
61
|
+
3. Educational institutions and legally recognised non-profit
|
|
62
|
+
organizations may use the Licensed Work in a production
|
|
63
|
+
environment without restriction, provided such use is not
|
|
64
|
+
for commercial profit.
|
|
65
|
+
|
|
66
|
+
For binding interpretive guidance on using Vezham products under
|
|
67
|
+
the Business Source License, please visit our licensing FAQ:
|
|
68
|
+
https://trust.vezham.com/legal/license-faq
|
|
69
|
+
|
|
70
|
+
Change Date: Four (4) years from the date the Licensed Work is first publicly
|
|
71
|
+
released under the specified Release Tag
|
|
72
|
+
|
|
73
|
+
Change License: GNU Affero General Public License v3.0 (AGPL-3.0-or-later)
|
|
74
|
+
The full text of the Change License is available at:
|
|
75
|
+
https://www.gnu.org/licenses/agpl-3.0.html
|
|
76
|
+
|
|
77
|
+
For information about commercial licensing or alternative arrangements,
|
|
78
|
+
please contact the Licensor at: https://vezham.com
|
|
79
|
+
or email: license@vezham.com
|
|
80
|
+
|
|
81
|
+
For formal legal notices or IP-related correspondence,
|
|
82
|
+
please contact: licensor@vezham.com
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
Notice
|
|
87
|
+
|
|
88
|
+
The Business Source License (this document, or the "License") is not an Open
|
|
89
|
+
Source license. However, the Licensed Work will eventually be made available
|
|
90
|
+
under an Open Source License, as stated in this License.
|
|
91
|
+
|
|
92
|
+
For more information on the use of the Business Source License generally,
|
|
93
|
+
please visit the Adopting and Developing Business Source License FAQ at:
|
|
94
|
+
https://mariadb.com/bsl-faq-adopting
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
Business Source License 1.1
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
Terms
|
|
103
|
+
|
|
104
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
105
|
+
works, redistribute, and make non-production use of the Licensed Work.
|
|
106
|
+
The Licensor may make an Additional Use Grant, above, permitting limited
|
|
107
|
+
production use.
|
|
108
|
+
|
|
109
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
110
|
+
available distribution of a specific version of the Licensed Work under this
|
|
111
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
112
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
113
|
+
above terminate.
|
|
114
|
+
|
|
115
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
116
|
+
currently in effect as described in this License, you must purchase a
|
|
117
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
118
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
119
|
+
|
|
120
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
121
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
122
|
+
separately for each version of the Licensed Work, and the Change Date may
|
|
123
|
+
vary for each version of the Licensed Work released by the Licensor.
|
|
124
|
+
|
|
125
|
+
You must conspicuously display this License on each original or modified copy
|
|
126
|
+
of the Licensed Work. If you receive the Licensed Work in original or modified
|
|
127
|
+
form from a third party, the terms and conditions set forth in this License
|
|
128
|
+
apply to your use of that work.
|
|
129
|
+
|
|
130
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
131
|
+
terminate your rights under this License for the current and all other versions
|
|
132
|
+
of the Licensed Work.
|
|
133
|
+
|
|
134
|
+
This License does not grant you any right in any trademark or logo of the
|
|
135
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
136
|
+
the Licensor as expressly required by this License).
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
Disclaimer
|
|
141
|
+
|
|
142
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
143
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
144
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
145
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
146
|
+
TITLE.
|
|
147
|
+
|
|
148
|
+
-----------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
MariaDB hereby grants you permission to use this License’s text to license your
|
|
151
|
+
works, and to refer to it using the trademark "Business Source License", as long
|
|
152
|
+
as you comply with the Covenants of Licensor below.
|
|
153
|
+
|
|
154
|
+
Covenants of Licensor
|
|
155
|
+
|
|
156
|
+
In consideration of the right to use this License’s text and the "Business
|
|
157
|
+
Source License" name and trademark, Licensor covenants to MariaDB, and to all
|
|
158
|
+
other recipients of the licensed work to be provided by Licensor:
|
|
159
|
+
|
|
160
|
+
1. To specify as the Change License the GPL Version 3.0 or any later version,
|
|
161
|
+
or a license that is compatible with GPL Version 3.0 or a later version,
|
|
162
|
+
where "compatible" means that software provided under the Change License can
|
|
163
|
+
be included in a program with software provided under GPL Version 3.0 or a
|
|
164
|
+
later version. Licensor may specify additional Change Licenses without
|
|
165
|
+
limitation.
|
|
166
|
+
|
|
167
|
+
2. To either: (a) specify an additional grant of rights to use that does not
|
|
168
|
+
impose any additional restriction on the right granted in this License, as
|
|
169
|
+
the Additional Use Grant; or (b) insert the text "None".
|
|
170
|
+
|
|
171
|
+
3. To specify a Change Date.
|
|
172
|
+
|
|
173
|
+
4. Not to modify this License in any other way.
|
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# TypeScript runtime prototype
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This package preserves the earlier Cordis/TanStack AI implementation and its comparison tests. The production Tamizhi backend is now Go in the sibling `vx-be-kit-lab` workspace. Static frontend applications use `@vezham/tamizhi-client` and must not import this package.
|
|
4
|
+
|
|
5
|
+
See [the Tamizhi package guide](../README.md) for the current integration.
|
|
6
|
+
|
|
7
|
+
Local example data defaults to `.vezham/tamizhi/data`, relative to the working directory. Set `TAMIZHI_DATA_DIRECTORY` to override it.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,yBAAyB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createTamizhi } from "./lib/runtime.js";
|
|
2
|
+
import { memoryStoragePlugin, permissionPolicyPlugin, toolPlugin } from "./lib/plugins.js";
|
|
3
|
+
import { httpTransportPlugin } from "./lib/http.js";
|
|
4
|
+
import { openaiCompatiblePlugin, tanstackProviderPlugin } from "./lib/tanstack-provider.js";
|
|
5
|
+
export { createTamizhi, httpTransportPlugin, memoryStoragePlugin, openaiCompatiblePlugin, permissionPolicyPlugin, tanstackProviderPlugin, toolPlugin };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Principal, TamizhiPlugin } from './types';
|
|
2
|
+
export interface HttpTransportOptions {
|
|
3
|
+
authenticate: (request: Request) => Principal | null | Promise<Principal | null>;
|
|
4
|
+
allowedOrigins?: string[];
|
|
5
|
+
basePath?: string;
|
|
6
|
+
maxBodyBytes?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const httpTransportPlugin: (options: HttpTransportOptions) => TamizhiPlugin;
|
|
9
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/lib/http.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAEvD,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,CACZ,OAAO,EAAE,OAAO,KACb,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAA;IACjD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AA4BD,eAAO,MAAM,mBAAmB,GAC9B,SAAS,oBAAoB,KAC5B,aAuHD,CAAA"}
|
package/dist/lib/http.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createLinkedAbortController } from "./linked-abort.js";
|
|
2
|
+
import { approvalDecisionSchema, runRequestSchema } from "@vezham/tamizhi-contracts";
|
|
3
|
+
//#region src/lib/http.ts
|
|
4
|
+
var readJson = async (request, maximum) => {
|
|
5
|
+
const reader = request.body?.getReader();
|
|
6
|
+
if (!reader) throw new Error("A JSON body is required");
|
|
7
|
+
const decoder = new TextDecoder();
|
|
8
|
+
let size = 0;
|
|
9
|
+
let text = "";
|
|
10
|
+
try {
|
|
11
|
+
for (;;) {
|
|
12
|
+
const { done, value } = await reader.read();
|
|
13
|
+
if (done) break;
|
|
14
|
+
size += value.byteLength;
|
|
15
|
+
if (size > maximum) {
|
|
16
|
+
await reader.cancel();
|
|
17
|
+
throw new Error("Request is too large");
|
|
18
|
+
}
|
|
19
|
+
text += decoder.decode(value, { stream: true });
|
|
20
|
+
}
|
|
21
|
+
return JSON.parse(text + decoder.decode());
|
|
22
|
+
} finally {
|
|
23
|
+
reader.releaseLock();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var httpTransportPlugin = (options) => ({
|
|
27
|
+
name: "transport.http",
|
|
28
|
+
inject: ["tamizhi"],
|
|
29
|
+
apply: (context) => {
|
|
30
|
+
const base = (options.basePath ?? "/api/tamizhi").replace(/\/$/, "");
|
|
31
|
+
const handler = async (request) => {
|
|
32
|
+
const origin = request.headers.get("origin");
|
|
33
|
+
const headers = new Headers({
|
|
34
|
+
"Cache-Control": "no-store",
|
|
35
|
+
Vary: "Origin"
|
|
36
|
+
});
|
|
37
|
+
const localOrigin = new URL(request.url).origin;
|
|
38
|
+
if (origin && origin !== localOrigin && !options.allowedOrigins?.includes(origin)) return new Response("Origin is not allowed", { status: 403 });
|
|
39
|
+
if (origin) {
|
|
40
|
+
headers.set("Access-Control-Allow-Origin", origin);
|
|
41
|
+
headers.set("Access-Control-Allow-Credentials", "true");
|
|
42
|
+
}
|
|
43
|
+
const json = (value, status = 200) => Response.json(value, {
|
|
44
|
+
status,
|
|
45
|
+
headers
|
|
46
|
+
});
|
|
47
|
+
if (request.method === "OPTIONS") {
|
|
48
|
+
headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
49
|
+
headers.set("Access-Control-Allow-Headers", "Authorization, Content-Type");
|
|
50
|
+
return new Response(null, {
|
|
51
|
+
status: 204,
|
|
52
|
+
headers
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const principal = await options.authenticate(request);
|
|
56
|
+
if (!principal?.id) return json({ error: "Authentication is required" }, 401);
|
|
57
|
+
const path = new URL(request.url).pathname;
|
|
58
|
+
const host = context.tamizhi.host;
|
|
59
|
+
if (request.method === "GET" && path === `${base}/models`) return json(host.models());
|
|
60
|
+
if (request.method === "GET" && path === `${base}/plugins`) return json(host.plugins());
|
|
61
|
+
if (request.method === "GET" && path.startsWith(`${base}/conversations/`)) {
|
|
62
|
+
let id;
|
|
63
|
+
try {
|
|
64
|
+
id = decodeURIComponent(path.slice(`${base}/conversations/`.length));
|
|
65
|
+
} catch {
|
|
66
|
+
return json({ error: "Invalid conversation id" }, 400);
|
|
67
|
+
}
|
|
68
|
+
if (!id || id.length > 128) return json({ error: "Invalid conversation id" }, 400);
|
|
69
|
+
const conversation = await host.conversation(principal, id);
|
|
70
|
+
return conversation ? json(conversation) : json({ error: "Conversation not found" }, 404);
|
|
71
|
+
}
|
|
72
|
+
if (request.method !== "POST") return json({ error: "Route not found" }, 404);
|
|
73
|
+
if (path !== `${base}/chat` && path !== `${base}/approvals`) return json({ error: "Route not found" }, 404);
|
|
74
|
+
try {
|
|
75
|
+
const body = await readJson(request, options.maxBodyBytes ?? 256e3);
|
|
76
|
+
if (path === `${base}/approvals`) {
|
|
77
|
+
const decision = approvalDecisionSchema.parse(body);
|
|
78
|
+
const accepted = host.approve(principal, decision.runId, decision.callId, decision.approved);
|
|
79
|
+
return json({ accepted }, accepted ? 200 : 404);
|
|
80
|
+
}
|
|
81
|
+
const input = runRequestSchema.parse(body);
|
|
82
|
+
const { controller, abort, cleanup } = createLinkedAbortController(request.signal);
|
|
83
|
+
const iterator = host.run(input, principal, controller.signal)[Symbol.asyncIterator]();
|
|
84
|
+
let pending = await iterator.next();
|
|
85
|
+
const encoder = new TextEncoder();
|
|
86
|
+
const stream = new ReadableStream({
|
|
87
|
+
async pull(output) {
|
|
88
|
+
try {
|
|
89
|
+
const next = pending;
|
|
90
|
+
if (next.done) {
|
|
91
|
+
output.close();
|
|
92
|
+
cleanup();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
output.enqueue(encoder.encode(`data: ${JSON.stringify(next.value)}\n\n`));
|
|
96
|
+
pending = await iterator.next();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
output.error(error);
|
|
99
|
+
abort();
|
|
100
|
+
cleanup();
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
async cancel() {
|
|
104
|
+
abort();
|
|
105
|
+
cleanup();
|
|
106
|
+
await iterator.return?.();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
headers.set("Content-Type", "text/event-stream; charset=utf-8");
|
|
110
|
+
headers.set("X-Accel-Buffering", "no");
|
|
111
|
+
return new Response(stream, { headers });
|
|
112
|
+
} catch {
|
|
113
|
+
return json({ error: "Invalid or conflicting chat request" }, 400);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
context.tamizhi.transports.register(context, "http", handler);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
//#endregion
|
|
120
|
+
export { httpTransportPlugin };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linked-abort.d.ts","sourceRoot":"","sources":["../../src/lib/linked-abort.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,2BAA2B,GAAI,QAAQ,WAAW;;;;CAO9D,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/lib/linked-abort.ts
|
|
2
|
+
var createLinkedAbortController = (signal) => {
|
|
3
|
+
const controller = new AbortController();
|
|
4
|
+
const abort = () => controller.abort();
|
|
5
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
6
|
+
if (signal.aborted) abort();
|
|
7
|
+
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
8
|
+
return {
|
|
9
|
+
controller,
|
|
10
|
+
abort,
|
|
11
|
+
cleanup
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { createLinkedAbortController };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createTamizhi } from './runtime';
|
|
2
|
+
import { TamizhiPlugin } from './types';
|
|
3
|
+
export declare const fileStoragePlugin: (directory: string) => TamizhiPlugin;
|
|
4
|
+
export declare const createTamizhiServer: (runtime: Pick<Awaited<ReturnType<typeof createTamizhi>>, "fetch">) => import('node:http').Server<typeof import('node:http').IncomingMessage, typeof import('node:http').ServerResponse>;
|
|
5
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/lib/node.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,eAAO,MAAM,iBAAiB,GAAI,WAAW,MAAM,KAAG,aA8CpD,CAAA;AAEF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,EAAE,OAAO,CAAC,sHAmE/D,CAAA"}
|
package/dist/lib/node.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { storedConversationSchema, wireMessageSchema } from "@vezham/tamizhi-contracts";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { Readable } from "node:stream";
|
|
7
|
+
//#region src/lib/node.ts
|
|
8
|
+
var fileStoragePlugin = (directory) => ({
|
|
9
|
+
name: "storage.file",
|
|
10
|
+
inject: ["tamizhi"],
|
|
11
|
+
apply: (context) => {
|
|
12
|
+
const filename = (owner, id) => join(directory, `${createHash("sha256").update(JSON.stringify([owner, id])).digest("hex")}.json`);
|
|
13
|
+
context.tamizhi.storage.register(context, "default", {
|
|
14
|
+
get: async (owner, id) => {
|
|
15
|
+
try {
|
|
16
|
+
const item = storedConversationSchema.parse(JSON.parse(await readFile(filename(owner, id), "utf8")));
|
|
17
|
+
return {
|
|
18
|
+
...item,
|
|
19
|
+
messages: item.messages.map((message) => wireMessageSchema.parse(message))
|
|
20
|
+
};
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
put: async (owner, conversation) => {
|
|
27
|
+
await mkdir(directory, {
|
|
28
|
+
recursive: true,
|
|
29
|
+
mode: 448
|
|
30
|
+
});
|
|
31
|
+
const target = filename(owner, conversation.id);
|
|
32
|
+
const temporary = `${target}.${randomUUID()}.tmp`;
|
|
33
|
+
await writeFile(temporary, JSON.stringify(storedConversationSchema.parse(conversation)), { mode: 384 });
|
|
34
|
+
await rename(temporary, target);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
var createTamizhiServer = (runtime) => createServer(async (incoming, outgoing) => {
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
outgoing.on("close", () => {
|
|
42
|
+
if (!outgoing.writableEnded) controller.abort();
|
|
43
|
+
});
|
|
44
|
+
try {
|
|
45
|
+
const headers = new Headers();
|
|
46
|
+
for (const [key, value] of Object.entries(incoming.headers)) if (Array.isArray(value)) for (const part of value) headers.append(key, part);
|
|
47
|
+
else if (value) headers.set(key, value);
|
|
48
|
+
const init = {
|
|
49
|
+
method: incoming.method,
|
|
50
|
+
headers,
|
|
51
|
+
signal: controller.signal
|
|
52
|
+
};
|
|
53
|
+
if (incoming.method !== "GET" && incoming.method !== "HEAD") {
|
|
54
|
+
init.body = Readable.toWeb(incoming);
|
|
55
|
+
init.duplex = "half";
|
|
56
|
+
}
|
|
57
|
+
const request = new Request(`http://localhost${incoming.url ?? "/"}`, init);
|
|
58
|
+
const response = await runtime.fetch(request);
|
|
59
|
+
outgoing.writeHead(response.status, Object.fromEntries(response.headers));
|
|
60
|
+
if (!response.body) {
|
|
61
|
+
outgoing.end();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const reader = response.body.getReader();
|
|
65
|
+
try {
|
|
66
|
+
for (;;) {
|
|
67
|
+
controller.signal.throwIfAborted();
|
|
68
|
+
const { done, value } = await reader.read();
|
|
69
|
+
if (done) break;
|
|
70
|
+
if (!outgoing.write(value)) await new Promise((resolve, reject) => {
|
|
71
|
+
const clean = () => {
|
|
72
|
+
outgoing.off("drain", drained);
|
|
73
|
+
outgoing.off("close", closed);
|
|
74
|
+
};
|
|
75
|
+
const drained = () => {
|
|
76
|
+
clean();
|
|
77
|
+
resolve();
|
|
78
|
+
};
|
|
79
|
+
const closed = () => {
|
|
80
|
+
clean();
|
|
81
|
+
reject(/* @__PURE__ */ new Error("Connection closed"));
|
|
82
|
+
};
|
|
83
|
+
outgoing.once("drain", drained);
|
|
84
|
+
outgoing.once("close", closed);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
outgoing.end();
|
|
88
|
+
} finally {
|
|
89
|
+
await reader.cancel().catch(() => void 0);
|
|
90
|
+
reader.releaseLock();
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
if (!outgoing.headersSent) outgoing.writeHead(500, { "Content-Type": "application/json" });
|
|
94
|
+
outgoing.end(JSON.stringify({ error: "Tamizhi request failed" }));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
//#endregion
|
|
98
|
+
export { createTamizhiServer, fileStoragePlugin };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TamizhiPlugin, Tool } from './types';
|
|
2
|
+
export declare const memoryStoragePlugin: () => TamizhiPlugin;
|
|
3
|
+
export declare const permissionPolicyPlugin: (options?: {
|
|
4
|
+
allowFullAccess?: boolean;
|
|
5
|
+
}) => TamizhiPlugin;
|
|
6
|
+
export declare const toolPlugin: (tool: Tool) => TamizhiPlugin;
|
|
7
|
+
//# sourceMappingURL=plugins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../../src/lib/plugins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA6B,aAAa,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAE7E,eAAO,MAAM,mBAAmB,QAAO,aAgCtC,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,UAAS;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAO,KAC1C,aAaD,CAAA;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,IAAI,KAAG,aAItC,CAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/lib/plugins.ts
|
|
2
|
+
var memoryStoragePlugin = () => {
|
|
3
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4
|
+
return {
|
|
5
|
+
name: "storage.memory",
|
|
6
|
+
inject: ["tamizhi"],
|
|
7
|
+
apply: (context) => {
|
|
8
|
+
context.tamizhi.storage.register(context, "default", {
|
|
9
|
+
get: async (owner, id) => {
|
|
10
|
+
const value = owners.get(owner)?.get(id);
|
|
11
|
+
return value ? structuredClone(value) : void 0;
|
|
12
|
+
},
|
|
13
|
+
put: async (owner, conversation) => {
|
|
14
|
+
let records = owners.get(owner);
|
|
15
|
+
if (!records) {
|
|
16
|
+
records = /* @__PURE__ */ new Map();
|
|
17
|
+
owners.set(owner, records);
|
|
18
|
+
}
|
|
19
|
+
records.delete(conversation.id);
|
|
20
|
+
records.set(conversation.id, structuredClone(conversation));
|
|
21
|
+
if (records.size > 100) {
|
|
22
|
+
const oldest = records.keys().next().value;
|
|
23
|
+
if (oldest) records.delete(oldest);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
context.effect(() => () => owners.clear());
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
var permissionPolicyPlugin = (options = {}) => ({
|
|
32
|
+
name: "permissions.default",
|
|
33
|
+
inject: ["tamizhi"],
|
|
34
|
+
apply: (context) => {
|
|
35
|
+
context.tamizhi.permissions.register(context, "default", { evaluate: (mode, tool) => {
|
|
36
|
+
if (mode === "full" && options.allowFullAccess) return "allow";
|
|
37
|
+
if (mode === "safe" && tool.risk === "read") return "allow";
|
|
38
|
+
return "ask";
|
|
39
|
+
} });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
var toolPlugin = (tool) => ({
|
|
43
|
+
name: `tool.${tool.name}`,
|
|
44
|
+
inject: ["tamizhi"],
|
|
45
|
+
apply: (context) => context.tamizhi.tools.register(context, tool.name, tool)
|
|
46
|
+
});
|
|
47
|
+
//#endregion
|
|
48
|
+
export { memoryStoragePlugin, permissionPolicyPlugin, toolPlugin };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/lib/queue.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,MAAM;kBAM/B,CAAC;kBAMD,KAAK;;kBAUS,cAAc,CAAC,CAAC,CAAC;CAahD,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/lib/queue.ts
|
|
2
|
+
var createEventQueue = () => {
|
|
3
|
+
const values = [];
|
|
4
|
+
let wake;
|
|
5
|
+
let ended = false;
|
|
6
|
+
let failure;
|
|
7
|
+
return {
|
|
8
|
+
push: (value) => {
|
|
9
|
+
if (ended) return;
|
|
10
|
+
if (values.length >= 4096) throw new Error("Stream consumer is too slow");
|
|
11
|
+
values.push(value);
|
|
12
|
+
wake?.();
|
|
13
|
+
},
|
|
14
|
+
fail: (error) => {
|
|
15
|
+
failure = error;
|
|
16
|
+
ended = true;
|
|
17
|
+
values.length = 0;
|
|
18
|
+
wake?.();
|
|
19
|
+
},
|
|
20
|
+
close: () => {
|
|
21
|
+
ended = true;
|
|
22
|
+
wake?.();
|
|
23
|
+
},
|
|
24
|
+
stream: async function* () {
|
|
25
|
+
while (!ended || values.length || failure) {
|
|
26
|
+
if (failure) throw failure;
|
|
27
|
+
if (values.length) {
|
|
28
|
+
const value = values.shift();
|
|
29
|
+
if (value) yield value;
|
|
30
|
+
} else await new Promise((resolve) => {
|
|
31
|
+
wake = resolve;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { createEventQueue };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,eAAO,MAAM,cAAc,GAAI,CAAC,EAAE,WAAW,MAAM,IAAI,KAAG,QAAQ,CAAC,CAAC,CAgBnE,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/lib/registry.ts
|
|
2
|
+
var createRegistry = (onRemove) => {
|
|
3
|
+
const values = /* @__PURE__ */ new Map();
|
|
4
|
+
return {
|
|
5
|
+
register: (context, id, value) => {
|
|
6
|
+
if (values.has(id)) throw new Error(`Duplicate registration: ${id}`);
|
|
7
|
+
context.effect(() => {
|
|
8
|
+
values.set(id, value);
|
|
9
|
+
return () => {
|
|
10
|
+
values.delete(id);
|
|
11
|
+
onRemove?.();
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
get: (id) => values.get(id),
|
|
16
|
+
entries: () => [...values.entries()]
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { createRegistry };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { StreamEvent } from '@vezham/tamizhi-contracts';
|
|
3
|
+
import { TamizhiPlugin } from './types';
|
|
4
|
+
export interface RuntimeOptions {
|
|
5
|
+
plugins: TamizhiPlugin[];
|
|
6
|
+
defaultModel?: string;
|
|
7
|
+
approvalTimeoutMs?: number;
|
|
8
|
+
runTimeoutMs?: number;
|
|
9
|
+
maxConcurrentRuns?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare const createTamizhi: (options: RuntimeOptions) => Promise<{
|
|
12
|
+
context: Context;
|
|
13
|
+
mount: (plugin: TamizhiPlugin) => Promise<() => Promise<void>>;
|
|
14
|
+
fetch: (request: Request) => Promise<Response>;
|
|
15
|
+
dispose: () => Promise<void>;
|
|
16
|
+
run: (request: import('@vezham/tamizhi-contracts').RunRequest, principal: import('./types').Principal, signal?: AbortSignal) => AsyncIterable<StreamEvent>;
|
|
17
|
+
approve: (principal: import('./types').Principal, runId: string, callId: string, approved: boolean) => boolean;
|
|
18
|
+
conversation: (principal: import('./types').Principal, id: string) => Promise<import('./types').StoredConversation | undefined>;
|
|
19
|
+
models: () => import('@vezham/tamizhi-contracts').ModelInfo[];
|
|
20
|
+
plugins: () => string[];
|
|
21
|
+
}>;
|
|
22
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/lib/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE7C,OAAO,KAAK,EAAmB,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAK7E,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,aAAa,EAAE,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,eAAO,MAAM,aAAa,GAAU,SAAS,cAAc;;oBAuN5B,aAAa;qBA2BvB,OAAO;;iHAzNQ,CAAC;;;;;EAwOpC,CAAA"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { createEventQueue } from "./queue.js";
|
|
2
|
+
import { createRegistry } from "./registry.js";
|
|
3
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
4
|
+
import { runRequestSchema } from "@vezham/tamizhi-contracts";
|
|
5
|
+
//#region src/lib/runtime.ts
|
|
6
|
+
var createTamizhi = async (options) => {
|
|
7
|
+
const context = new Context();
|
|
8
|
+
const running = /* @__PURE__ */ new Map();
|
|
9
|
+
const approvals = /* @__PURE__ */ new Map();
|
|
10
|
+
let disposed = false;
|
|
11
|
+
const cancelAll = () => {
|
|
12
|
+
for (const run of running.values()) run.abort();
|
|
13
|
+
};
|
|
14
|
+
const plugins = /* @__PURE__ */ new Map();
|
|
15
|
+
const services = {
|
|
16
|
+
providers: createRegistry(cancelAll),
|
|
17
|
+
tools: createRegistry(cancelAll),
|
|
18
|
+
storage: createRegistry(cancelAll),
|
|
19
|
+
permissions: createRegistry(cancelAll),
|
|
20
|
+
transports: createRegistry()
|
|
21
|
+
};
|
|
22
|
+
const host = {
|
|
23
|
+
conversation: (principal, id) => services.storage.get("default")?.get(principal.id, id) ?? Promise.resolve(void 0),
|
|
24
|
+
models: () => services.providers.entries().flatMap(([, provider]) => provider.models),
|
|
25
|
+
plugins: () => [...plugins.keys()],
|
|
26
|
+
approve: (principal, runId, callId, approved) => {
|
|
27
|
+
const pending = approvals.get(`${runId}:${callId}`);
|
|
28
|
+
if (!pending || pending.owner !== principal.id) return false;
|
|
29
|
+
pending.decide(approved);
|
|
30
|
+
return true;
|
|
31
|
+
},
|
|
32
|
+
run: async function* (input, principal, externalSignal) {
|
|
33
|
+
if (disposed) throw new Error("Tamizhi runtime has been disposed");
|
|
34
|
+
const request = runRequestSchema.parse(input);
|
|
35
|
+
const key = JSON.stringify([principal.id, request.conversationId]);
|
|
36
|
+
if (running.has(key)) throw new Error("A reply is already running for this conversation");
|
|
37
|
+
if (running.size >= (options.maxConcurrentRuns ?? 32)) throw new Error("Tamizhi is busy");
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
const abort = () => controller.abort();
|
|
40
|
+
externalSignal?.addEventListener("abort", abort, { once: true });
|
|
41
|
+
if (externalSignal?.aborted) controller.abort();
|
|
42
|
+
running.set(key, controller);
|
|
43
|
+
const timeout = setTimeout(abort, options.runTimeoutMs ?? 12e4);
|
|
44
|
+
const queue = createEventQueue();
|
|
45
|
+
const identity = {
|
|
46
|
+
runId: crypto.randomUUID(),
|
|
47
|
+
conversationId: request.conversationId
|
|
48
|
+
};
|
|
49
|
+
const emit = (event) => {
|
|
50
|
+
queue.push(event);
|
|
51
|
+
context.parallel("tamizhi/event", event, principal).catch(() => context.logger("tamizhi").warn("An event observer failed"));
|
|
52
|
+
};
|
|
53
|
+
const waitForApproval = (approval) => new Promise((resolve, reject) => {
|
|
54
|
+
const id = `${approval.runId}:${approval.callId}`;
|
|
55
|
+
let settled = false;
|
|
56
|
+
const finish = (approved) => {
|
|
57
|
+
if (settled) return;
|
|
58
|
+
settled = true;
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
controller.signal.removeEventListener("abort", canceled);
|
|
61
|
+
approvals.delete(id);
|
|
62
|
+
resolve(approved);
|
|
63
|
+
};
|
|
64
|
+
const canceled = () => {
|
|
65
|
+
finish(false);
|
|
66
|
+
reject(/* @__PURE__ */ new Error("Run canceled"));
|
|
67
|
+
};
|
|
68
|
+
const timer = setTimeout(() => finish(false), options.approvalTimeoutMs ?? 6e4);
|
|
69
|
+
approvals.set(id, {
|
|
70
|
+
owner: principal.id,
|
|
71
|
+
decide: finish
|
|
72
|
+
});
|
|
73
|
+
controller.signal.addEventListener("abort", canceled, { once: true });
|
|
74
|
+
if (controller.signal.aborted) canceled();
|
|
75
|
+
else emit({
|
|
76
|
+
...identity,
|
|
77
|
+
type: "approval",
|
|
78
|
+
...approval
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
const produce = async () => {
|
|
82
|
+
emit({
|
|
83
|
+
...identity,
|
|
84
|
+
type: "start"
|
|
85
|
+
});
|
|
86
|
+
try {
|
|
87
|
+
const model = request.model === "Auto" ? options.defaultModel : request.model ?? options.defaultModel;
|
|
88
|
+
const provider = services.providers.entries().find(([, item]) => !model || item.models.some((info) => info.id === model))?.[1];
|
|
89
|
+
if (!provider) throw new Error("No provider is configured for this model");
|
|
90
|
+
const storage = services.storage.get("default");
|
|
91
|
+
if (!storage) throw new Error("No conversation storage plugin is configured");
|
|
92
|
+
const policy = services.permissions.get("default");
|
|
93
|
+
if (!policy) throw new Error("No permission policy plugin is configured");
|
|
94
|
+
let text = "";
|
|
95
|
+
const executeTool = async (name, input, callId) => {
|
|
96
|
+
controller.signal.throwIfAborted();
|
|
97
|
+
const tool = services.tools.get(name);
|
|
98
|
+
if (!tool) throw new Error(`Unknown tool: ${name}`);
|
|
99
|
+
input = await tool.inputSchema.parseAsync(input);
|
|
100
|
+
emit({
|
|
101
|
+
...identity,
|
|
102
|
+
type: "tool",
|
|
103
|
+
name,
|
|
104
|
+
input,
|
|
105
|
+
callId
|
|
106
|
+
});
|
|
107
|
+
const decision = policy.evaluate(request.permission, tool, principal);
|
|
108
|
+
if (decision === "deny" || decision === "ask" && !await waitForApproval({
|
|
109
|
+
runId: identity.runId,
|
|
110
|
+
callId,
|
|
111
|
+
name,
|
|
112
|
+
input
|
|
113
|
+
})) {
|
|
114
|
+
const output = { error: "Tool request was not approved" };
|
|
115
|
+
emit({
|
|
116
|
+
...identity,
|
|
117
|
+
type: "tool-result",
|
|
118
|
+
name,
|
|
119
|
+
callId,
|
|
120
|
+
output
|
|
121
|
+
});
|
|
122
|
+
return output;
|
|
123
|
+
}
|
|
124
|
+
controller.signal.throwIfAborted();
|
|
125
|
+
const output = await tool.execute(input, {
|
|
126
|
+
principal,
|
|
127
|
+
conversationId: request.conversationId,
|
|
128
|
+
signal: controller.signal
|
|
129
|
+
});
|
|
130
|
+
emit({
|
|
131
|
+
...identity,
|
|
132
|
+
type: "tool-result",
|
|
133
|
+
name,
|
|
134
|
+
callId,
|
|
135
|
+
output
|
|
136
|
+
});
|
|
137
|
+
return output;
|
|
138
|
+
};
|
|
139
|
+
await storage.put(principal.id, {
|
|
140
|
+
id: request.conversationId,
|
|
141
|
+
messages: request.messages,
|
|
142
|
+
updatedAt: Date.now()
|
|
143
|
+
});
|
|
144
|
+
try {
|
|
145
|
+
for await (const delta of provider.stream({
|
|
146
|
+
...request,
|
|
147
|
+
model: model ?? provider.models[0]?.id,
|
|
148
|
+
signal: controller.signal,
|
|
149
|
+
tools: services.tools.entries().map(([, tool]) => tool),
|
|
150
|
+
executeTool
|
|
151
|
+
})) {
|
|
152
|
+
controller.signal.throwIfAborted();
|
|
153
|
+
if (text.length + delta.length > 1e5) throw new Error("Response is too large");
|
|
154
|
+
text += delta;
|
|
155
|
+
emit({
|
|
156
|
+
...identity,
|
|
157
|
+
type: "text",
|
|
158
|
+
delta
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
} finally {
|
|
162
|
+
if (text) await storage.put(principal.id, {
|
|
163
|
+
id: request.conversationId,
|
|
164
|
+
messages: [...request.messages, {
|
|
165
|
+
role: "assistant",
|
|
166
|
+
content: text
|
|
167
|
+
}].slice(-60),
|
|
168
|
+
updatedAt: Date.now()
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
emit({
|
|
172
|
+
...identity,
|
|
173
|
+
type: "finish",
|
|
174
|
+
reason: controller.signal.aborted ? "canceled" : "complete"
|
|
175
|
+
});
|
|
176
|
+
} catch (error) {
|
|
177
|
+
emit(controller.signal.aborted ? {
|
|
178
|
+
...identity,
|
|
179
|
+
type: "finish",
|
|
180
|
+
reason: "canceled"
|
|
181
|
+
} : {
|
|
182
|
+
...identity,
|
|
183
|
+
type: "error",
|
|
184
|
+
message: error instanceof Error && error.message.startsWith("No ") ? error.message : "The response could not be completed. Please retry."
|
|
185
|
+
});
|
|
186
|
+
} finally {
|
|
187
|
+
clearTimeout(timeout);
|
|
188
|
+
externalSignal?.removeEventListener("abort", abort);
|
|
189
|
+
running.delete(key);
|
|
190
|
+
queue.close();
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const producer = produce().catch(() => {
|
|
194
|
+
controller.abort();
|
|
195
|
+
queue.fail(/* @__PURE__ */ new Error("The response stream could not be completed"));
|
|
196
|
+
});
|
|
197
|
+
try {
|
|
198
|
+
yield* queue.stream();
|
|
199
|
+
} finally {
|
|
200
|
+
controller.abort();
|
|
201
|
+
await producer;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const foundation = await context.plugin({
|
|
206
|
+
name: "tamizhi",
|
|
207
|
+
apply: (scope) => scope.provide("tamizhi", {
|
|
208
|
+
...services,
|
|
209
|
+
host
|
|
210
|
+
})
|
|
211
|
+
});
|
|
212
|
+
const mount = async (plugin) => {
|
|
213
|
+
const name = plugin.name;
|
|
214
|
+
if (!name) throw new Error("Tamizhi plugins need a name");
|
|
215
|
+
if (disposed) throw new Error("Tamizhi runtime has been disposed");
|
|
216
|
+
if (plugins.has(name)) throw new Error(`Duplicate plugin: ${name}`);
|
|
217
|
+
const fiber = await foundation.ctx.plugin(plugin);
|
|
218
|
+
plugins.set(name, fiber);
|
|
219
|
+
return async () => {
|
|
220
|
+
cancelAll();
|
|
221
|
+
await fiber.dispose();
|
|
222
|
+
plugins.delete(name);
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
try {
|
|
226
|
+
await options.plugins.reduce((ready, plugin) => ready.then(() => mount(plugin)), Promise.resolve());
|
|
227
|
+
} catch (error) {
|
|
228
|
+
await foundation.dispose();
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
...host,
|
|
233
|
+
context,
|
|
234
|
+
mount,
|
|
235
|
+
fetch: (request) => {
|
|
236
|
+
const handler = services.transports.get("http");
|
|
237
|
+
return handler ? handler(request) : Promise.resolve(new Response("HTTP transport is not installed", { status: 503 }));
|
|
238
|
+
},
|
|
239
|
+
dispose: async () => {
|
|
240
|
+
disposed = true;
|
|
241
|
+
cancelAll();
|
|
242
|
+
await foundation.dispose();
|
|
243
|
+
plugins.clear();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
//#endregion
|
|
248
|
+
export { createTamizhi };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AnyTextAdapter } from '@tanstack/ai';
|
|
2
|
+
import { ModelInfo } from '@vezham/tamizhi-contracts';
|
|
3
|
+
import { TamizhiPlugin } from './types';
|
|
4
|
+
export interface TanStackProviderOptions {
|
|
5
|
+
id: string;
|
|
6
|
+
models: ModelInfo[];
|
|
7
|
+
adapter: (model: string, fast: boolean) => AnyTextAdapter;
|
|
8
|
+
systemPrompt?: string;
|
|
9
|
+
maxIterations?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare const tanstackProviderPlugin: (options: TanStackProviderOptions) => TamizhiPlugin;
|
|
12
|
+
export interface OpenAICompatibleOptions {
|
|
13
|
+
id: string;
|
|
14
|
+
apiKey: string;
|
|
15
|
+
baseURL?: string;
|
|
16
|
+
models: ModelInfo[];
|
|
17
|
+
systemPrompt?: string;
|
|
18
|
+
fastModel?: string;
|
|
19
|
+
fetch?: typeof globalThis.fetch;
|
|
20
|
+
}
|
|
21
|
+
export declare const openaiCompatiblePlugin: (options: OpenAICompatibleOptions) => TamizhiPlugin;
|
|
22
|
+
//# sourceMappingURL=tanstack-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tanstack-provider.d.ts","sourceRoot":"","sources":["../../src/lib/tanstack-provider.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAG1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,cAAc,CAAA;IACzD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,sBAAsB,GACjC,SAAS,uBAAuB,KAC/B,aA8CD,CAAA;AAEF,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAChC;AAED,eAAO,MAAM,sBAAsB,GACjC,SAAS,uBAAuB,KAC/B,aAcC,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createLinkedAbortController } from "./linked-abort.js";
|
|
2
|
+
import { chat, createModel, extendAdapter, maxIterations, toolDefinition } from "@tanstack/ai";
|
|
3
|
+
import { createOpenaiChatCompletions } from "@tanstack/ai-openai";
|
|
4
|
+
//#region src/lib/tanstack-provider.ts
|
|
5
|
+
var tanstackProviderPlugin = (options) => ({
|
|
6
|
+
name: `provider.${options.id}`,
|
|
7
|
+
inject: ["tamizhi"],
|
|
8
|
+
apply: (context) => {
|
|
9
|
+
context.tamizhi.providers.register(context, options.id, {
|
|
10
|
+
models: options.models,
|
|
11
|
+
stream: async function* (request) {
|
|
12
|
+
const model = request.model ?? options.models[0]?.id;
|
|
13
|
+
if (!model) throw new Error("No model is configured");
|
|
14
|
+
const { controller, cleanup } = createLinkedAbortController(request.signal);
|
|
15
|
+
const tools = request.tools.map((tool) => toolDefinition({
|
|
16
|
+
name: tool.name,
|
|
17
|
+
description: tool.description,
|
|
18
|
+
inputSchema: tool.inputSchema
|
|
19
|
+
}).server((input, execution) => request.executeTool(tool.name, input, execution?.toolCallId ?? crypto.randomUUID())));
|
|
20
|
+
try {
|
|
21
|
+
for await (const event of chat({
|
|
22
|
+
adapter: options.adapter(model, request.fast),
|
|
23
|
+
messages: request.messages,
|
|
24
|
+
systemPrompts: [options.systemPrompt ?? "You are Tamizhi, a helpful assistant. Use tools only when needed."],
|
|
25
|
+
tools,
|
|
26
|
+
abortController: controller,
|
|
27
|
+
agentLoopStrategy: maxIterations(options.maxIterations ?? 8)
|
|
28
|
+
})) {
|
|
29
|
+
if (event.type === "TEXT_MESSAGE_CONTENT") yield event.delta;
|
|
30
|
+
if (event.type === "RUN_ERROR") throw new Error(event.message);
|
|
31
|
+
}
|
|
32
|
+
} finally {
|
|
33
|
+
cleanup();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
var openaiCompatiblePlugin = (options) => tanstackProviderPlugin({
|
|
40
|
+
id: options.id,
|
|
41
|
+
models: options.models,
|
|
42
|
+
systemPrompt: options.systemPrompt,
|
|
43
|
+
adapter: (model, fast) => extendAdapter(createOpenaiChatCompletions, options.models.map((item) => createModel(item.id, ["text"])))(fast && options.fastModel ? options.fastModel : model, options.apiKey, {
|
|
44
|
+
baseURL: options.baseURL,
|
|
45
|
+
fetch: options.fetch,
|
|
46
|
+
maxRetries: 0
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
//#endregion
|
|
50
|
+
export { openaiCompatiblePlugin, tanstackProviderPlugin };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Context, Plugin } from '@deepseek-ai/cordis';
|
|
2
|
+
import { ZodType } from 'zod';
|
|
3
|
+
import { ModelInfo, PermissionMode, RunRequest, StreamEvent, WireMessage } from '@vezham/tamizhi-contracts';
|
|
4
|
+
export type TamizhiPlugin = Plugin.Object<void>;
|
|
5
|
+
export interface Registry<T> {
|
|
6
|
+
register: (context: Context, id: string, value: T) => void;
|
|
7
|
+
get: (id: string) => T | undefined;
|
|
8
|
+
entries: () => [string, T][];
|
|
9
|
+
}
|
|
10
|
+
export interface Principal {
|
|
11
|
+
id: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ToolContext {
|
|
14
|
+
principal: Principal;
|
|
15
|
+
conversationId: string;
|
|
16
|
+
signal: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
export interface Tool {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
risk: 'read' | 'write' | 'external';
|
|
22
|
+
inputSchema: ZodType;
|
|
23
|
+
execute: (input: unknown, context: ToolContext) => Promise<unknown>;
|
|
24
|
+
}
|
|
25
|
+
export interface ProviderRequest extends RunRequest {
|
|
26
|
+
signal: AbortSignal;
|
|
27
|
+
executeTool: (name: string, input: unknown, callId: string) => Promise<unknown>;
|
|
28
|
+
tools: Tool[];
|
|
29
|
+
}
|
|
30
|
+
export interface Provider {
|
|
31
|
+
models: ModelInfo[];
|
|
32
|
+
stream: (request: ProviderRequest) => AsyncIterable<string>;
|
|
33
|
+
}
|
|
34
|
+
export interface StoredConversation {
|
|
35
|
+
id: string;
|
|
36
|
+
messages: WireMessage[];
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
}
|
|
39
|
+
export interface Storage {
|
|
40
|
+
get: (owner: string, id: string) => Promise<StoredConversation | undefined>;
|
|
41
|
+
put: (owner: string, conversation: StoredConversation) => Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
export interface PermissionPolicy {
|
|
44
|
+
evaluate: (mode: PermissionMode, tool: Tool, principal: Principal) => 'allow' | 'ask' | 'deny';
|
|
45
|
+
}
|
|
46
|
+
export interface Host {
|
|
47
|
+
run: (request: RunRequest, principal: Principal, signal?: AbortSignal) => AsyncIterable<StreamEvent>;
|
|
48
|
+
approve: (principal: Principal, runId: string, callId: string, approved: boolean) => boolean;
|
|
49
|
+
conversation: (principal: Principal, id: string) => Promise<StoredConversation | undefined>;
|
|
50
|
+
models: () => ModelInfo[];
|
|
51
|
+
plugins: () => string[];
|
|
52
|
+
}
|
|
53
|
+
export interface TamizhiServices {
|
|
54
|
+
providers: Registry<Provider>;
|
|
55
|
+
tools: Registry<Tool>;
|
|
56
|
+
storage: Registry<Storage>;
|
|
57
|
+
permissions: Registry<PermissionPolicy>;
|
|
58
|
+
transports: Registry<(request: Request) => Promise<Response>>;
|
|
59
|
+
host: Host;
|
|
60
|
+
}
|
|
61
|
+
declare module '@deepseek-ai/cordis' {
|
|
62
|
+
interface Context {
|
|
63
|
+
tamizhi: TamizhiServices;
|
|
64
|
+
}
|
|
65
|
+
interface Events {
|
|
66
|
+
'tamizhi/event': (event: StreamEvent, principal: Principal) => void;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AAElC,OAAO,KAAK,EACV,SAAS,EACT,cAAc,EACd,UAAU,EACV,WAAW,EACX,WAAW,EACZ,MAAM,2BAA2B,CAAA;AAElC,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;AAC/C,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAA;IAC1D,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,CAAA;IAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAA;CAC7B;AACD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;CACX;AACD,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,SAAS,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,WAAW,CAAA;CACpB;AACD,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAA;IACnC,WAAW,EAAE,OAAO,CAAA;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACpE;AACD,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,MAAM,EAAE,WAAW,CAAA;IACnB,WAAW,EAAE,CACX,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,OAAO,CAAC,CAAA;IACrB,KAAK,EAAE,IAAI,EAAE,CAAA;CACd;AACD,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,MAAM,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,aAAa,CAAC,MAAM,CAAC,CAAA;CAC5D;AACD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,WAAW,EAAE,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AACD,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAA;IAC3E,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACxE;AACD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,CACR,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,SAAS,KACjB,OAAO,GAAG,KAAK,GAAG,MAAM,CAAA;CAC9B;AACD,MAAM,WAAW,IAAI;IACnB,GAAG,EAAE,CACH,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,SAAS,EACpB,MAAM,CAAC,EAAE,WAAW,KACjB,aAAa,CAAC,WAAW,CAAC,CAAA;IAC/B,OAAO,EAAE,CACP,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,KACd,OAAO,CAAA;IACZ,YAAY,EAAE,CACZ,SAAS,EAAE,SAAS,EACpB,EAAE,EAAE,MAAM,KACP,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAA;IAC5C,MAAM,EAAE,MAAM,SAAS,EAAE,CAAA;IACzB,OAAO,EAAE,MAAM,MAAM,EAAE,CAAA;CACxB;AACD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAC7B,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;IACrB,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC1B,WAAW,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAA;IACvC,UAAU,EAAE,QAAQ,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC7D,IAAI,EAAE,IAAI,CAAA;CACX;AACD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,OAAO,EAAE,eAAe,CAAA;KACzB;IACD,UAAU,MAAM;QACd,eAAe,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,KAAK,IAAI,CAAA;KACpE;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vezham/tamizhi-core",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
|
|
3
|
+
"version": "26.3.1",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/vezham/vx-fe-pkgs.git",
|
|
7
|
+
"directory": "packages/tamizhi/core"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"@vx/fe-kit": "./src/index.ts",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json",
|
|
18
|
+
"./node": {
|
|
19
|
+
"@vx/fe-kit": "./src/lib/node.ts",
|
|
20
|
+
"types": "./dist/lib/node.d.ts",
|
|
21
|
+
"import": "./dist/lib/node.js",
|
|
22
|
+
"default": "./dist/lib/node.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"!**/*.tsbuildinfo",
|
|
30
|
+
"dist",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@deepseek-ai/cordis": "4.0.5",
|
|
35
|
+
"@tanstack/ai": "0.71.0",
|
|
36
|
+
"@tanstack/ai-openai": "0.31.0",
|
|
37
|
+
"@vezham/tamizhi-contracts": "26.3.1",
|
|
38
|
+
"zod": "^4.6.7"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@vezham/tamizhi-client": "26.3.1"
|
|
42
|
+
}
|
|
6
43
|
}
|