@vezham/tamizhi-client 0.0.0-stage → 26.3.2

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 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,107 @@
1
- # Temporary Holding Version
1
+ # Tamizhi client and local companion
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The standalone and embedded chat share this client. It connects to the Go
4
+ companion over loopback HTTP/SSE and exposes `createTamizhiConnection` for
5
+ TanStack AI. The normal Tamizhi UI uses `runTamizhiChat`, which lets TanStack
6
+ assemble live messages while preserving the existing durable history, profiles,
7
+ approvals, tools and sources. Go owns the only agent loop.
8
+
9
+ ## Start locally
10
+
11
+ In the Go backend checkout:
12
+
13
+ ```sh
14
+ pnpm exec nx run tamizhi-api:build
15
+ ./dist/tamizhi serve
16
+ ```
17
+
18
+ Open **Connect companion** beside the composer in standalone or embed and click
19
+ **Connect**. The browser supplies its current origin. For an unknown origin,
20
+ compare the verification code and origin shown in the UI with the CLI prompt,
21
+ then enter `y` to approve and remember it, or `n`/Enter to reject. No terminal
22
+ input means rejection. `--origin` is optional preapproval, not a required UI flag.
23
+
24
+ Approved origins are saved in owner-only `~/.vezham/tamizhi/allowed-origins.json`
25
+ (or the selected `--data-dir`). Connections expire after five minutes; closing
26
+ or cancelling the modal cancels its request. Reloading the UI or restarting the
27
+ CLI requires clicking **Connect** again, but remembered origins do not prompt.
28
+ The verification code is for comparison, not something to copy into an input.
29
+
30
+ The default endpoint is `http://127.0.0.1:7331/api/tamizhi`. Hosted apps require
31
+ HTTPS and may need browser local-network permission; localhost connects directly.
32
+ HTTP `*.vezham.local` apps use the shared Vite development relay. After pairing,
33
+ **AI settings** beside the connection control configures BYOK providers. Leo is the default built-in tool-workflow assistant and requires no model-provider
34
+ key. Optional BYOK models provide broader reasoning and language generation. No extension or hosted
35
+ Tamizhi service is required for local research.
36
+
37
+ CLI clients use the same runtime:
38
+
39
+ ```sh
40
+ ./dist/tamizhi status
41
+ ./dist/tamizhi chat --permission safe 'What is the latest React version?'
42
+ ./dist/tamizhi chat --permission safe --json 'What is the latest React version?'
43
+ ```
44
+
45
+ The default permission mode is `ask`; terminal approvals read `y`/`N` from stdin.
46
+ `--json` emits structured stream events, and human progress/prompts go to stderr.
47
+ Ctrl+C stops the active request and attempts to cancel its backend run. `serve`,
48
+ `pair`, `status` and `chat` accept `--data-dir` and `--listen` as applicable; see
49
+ `./dist/tamizhi <command> --help`. State defaults to `~/.vezham/tamizhi/`:
50
+ SQLite conversations, an encryption key, and an owner-only CLI token. No Neon
51
+ setup is needed for local mode. `tamizhi api --config path` retains configured
52
+ hosted API/OIDC/Neon operation when wanted.
53
+
54
+ ## Connection boundaries
55
+
56
+ - The service binds only to loopback. Only the bounded connection handshake is
57
+ exposed to an unknown origin; all normal API requests require a trusted origin
58
+ and authentication. Approval is saved before granting access.
59
+ - Connection capabilities are one-use, origin-bound and expire after five minutes.
60
+ UI sessions expire
61
+ after eight hours, on explicit disconnect, or when the companion restarts.
62
+ - UI bearer credentials stay in memory; the endpoint preference alone uses
63
+ localStorage. A reload requires re-pairing. CLI secrets never enter UI code.
64
+ - The browser grants local-network access; CORS and that permission are separate.
65
+ - If the companion is stopped or pairing expires, restart/re-pair through the
66
+ visible connection control. The local demo remains available when disconnected.
67
+ - The public web connector reads server-rendered HTML/plain text, with SSRF,
68
+ byte/source/operation/time limits. It does not use your browser login or render
69
+ JavaScript. CAPTCHA, blocked sites and search-provider errors return failures.
70
+
71
+ ## TanStack integration
72
+
73
+ `createTamizhiConnection(client, options)` returns a TanStack
74
+ `ConnectConnectionAdapter`, usable as the `connection` for `useChat` or a headless
75
+ `ChatClient`. It maps Go start/text/tool/result/finish/error events into AG-UI.
76
+ Tamizhi-specific progress, approval and source events remain available through
77
+ `onEvent`. Pairing/authentication and provider secrets belong to the companion;
78
+ this adapter does not implement TanStack's separate provider-key forwarding flow.
79
+
80
+ The existing low-level `createTamizhiClient` still owns authenticated requests,
81
+ strict event validation, sequence deduplication and reconnect/replay. Aborting a
82
+ TanStack request aborts its transport. The shared UI and CLI additionally cancel
83
+ the active Go run explicitly. Both standalone and embed retain HeroUI components.
84
+
85
+ ## Verify
86
+
87
+ ```sh
88
+ pnpm exec nx run-many -t test,typecheck,lint -p tamizhi-client,tamizhi-contracts,tamizhi-ui
89
+ TAMIZHI_BACKEND_DIR=/path/to/vx-be-kit-lab pnpm exec nx run tamizhi-client:companion-test
90
+ ```
91
+
92
+ The end-to-end fixture uses a real compiled CLI, pairing, Go HTTP/SSE, TanStack
93
+ and Chromium with isolated temporary state. Inference and retrieved sites are
94
+ controlled fixtures. It checks streamed answer/source delivery, CLI chat, and
95
+ connection replay rejection, terminal approve/reject, and saved-origin reconnect
96
+ after a real CLI restart. Live search/provider quality requires separate BYOK
97
+ verification; these tests consume no real provider quota.
98
+
99
+ HTTP `*.vezham.local` development apps can use the shared Vite app preset's
100
+ local relay with the default companion URL. Restart the dev server once after
101
+ this preset update. The relay preserves the app's origin for CLI approval and
102
+ authenticated requests. The relay is development-only;
103
+ production and preview still require HTTPS or localhost for direct connections.
104
+
105
+ Leo is advertised by the normal model catalog as `leo/default`. The same TanStack
106
+ adapter streams its responses and tool events. It uses Go workflow planners and
107
+ tool results, with no separate frontend mock or second execution loop.
@@ -0,0 +1,7 @@
1
+ export * from './lib/client';
2
+ export * from './lib/stream';
3
+ export * from './lib/connection';
4
+ export * from './lib/companion';
5
+ export * from './lib/companion-transport';
6
+ export * from './lib/companion-connect';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAE5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAE/B,cAAc,2BAA2B,CAAA;AAEzC,cAAc,yBAAyB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import { readEventStream } from "./lib/stream.js";
2
+ import { createTamizhiClient } from "./lib/client.js";
3
+ import { createTamizhiConnection, runTamizhiChat } from "./lib/connection.js";
4
+ import { DEFAULT_COMPANION_URL, companionURL } from "./lib/companion.js";
5
+ import { companionTransport } from "./lib/companion-transport.js";
6
+ import { connectCompanion } from "./lib/companion-connect.js";
7
+ export { DEFAULT_COMPANION_URL, companionTransport, companionURL, connectCompanion, createTamizhiClient, createTamizhiConnection, readEventStream, runTamizhiChat };
@@ -0,0 +1,51 @@
1
+ import type { ApprovalDecision, ModelInfo, RunRequest, StreamEvent, TamizhiAPI } from '@vezham/tamizhi-contracts';
2
+ export interface TamizhiClient {
3
+ stream: (request: RunRequest, signal?: AbortSignal) => AsyncIterable<StreamEvent>;
4
+ conversation: (id: string, signal?: AbortSignal) => Promise<import('@vezham/tamizhi-contracts').StoredConversationData>;
5
+ models: (signal?: AbortSignal) => Promise<ModelInfo[]>;
6
+ subscribeProviderChanges?: (listener: () => void) => () => void;
7
+ api?: TamizhiAPIClient;
8
+ approve: (decision: ApprovalDecision, signal?: AbortSignal) => Promise<void>;
9
+ }
10
+ export interface TamizhiAPIClient {
11
+ authorizeProvider: (id: string, projectId: string, returnTo: string) => Promise<TamizhiAPI.ProviderAuthorization>;
12
+ providers: (signal?: AbortSignal) => Promise<TamizhiAPI.ProviderSettings[]>;
13
+ connectProvider: (id: string, apiKey: string, signal?: AbortSignal) => Promise<TamizhiAPI.ProviderSettings>;
14
+ setProviderSearch: (id: string, enabled: boolean) => Promise<TamizhiAPI.ProviderSettings>;
15
+ removeProvider: (id: string) => Promise<void>;
16
+ capabilities: (signal?: AbortSignal) => Promise<TamizhiAPI.Capabilities>;
17
+ session: (signal?: AbortSignal) => Promise<TamizhiAPI.SessionInfo>;
18
+ authSession: (signal?: AbortSignal) => Promise<TamizhiAPI.AuthSession>;
19
+ getLoginURL: (returnTo?: string, connection?: string) => string;
20
+ loginURL: string;
21
+ logout: () => Promise<void>;
22
+ conversations: (query?: string, cursor?: string, signal?: AbortSignal) => Promise<TamizhiAPI.ConversationPage>;
23
+ rename: (id: string, title: string) => Promise<TamizhiAPI.Conversation>;
24
+ deleteConversation: (id: string) => Promise<void>;
25
+ feedback: (id: string, message: string, rating: string) => Promise<TamizhiAPI.Conversation>;
26
+ tasks: (id: string, message: string, tasks: TamizhiAPI.Task[]) => Promise<TamizhiAPI.Conversation>;
27
+ branch: (id: string, messageId: string) => Promise<TamizhiAPI.Conversation>;
28
+ checkpoints: (id: string) => Promise<TamizhiAPI.Checkpoint[]>;
29
+ checkpoint: (id: string, label: string) => Promise<TamizhiAPI.Checkpoint>;
30
+ restore: (id: string, checkpoint: string) => Promise<TamizhiAPI.Conversation>;
31
+ compact: (id: string) => Promise<TamizhiAPI.Conversation>;
32
+ upload: (file: File, signal?: AbortSignal) => Promise<TamizhiAPI.Attachment>;
33
+ transcribe: (file: File, signal?: AbortSignal) => Promise<TamizhiAPI.Transcript>;
34
+ speech: (text: string, signal?: AbortSignal) => Promise<Blob>;
35
+ memory: (query?: string) => Promise<TamizhiAPI.Memory[]>;
36
+ remember: (text: string, tags?: string[]) => Promise<TamizhiAPI.Memory>;
37
+ forget: (id: string) => Promise<void>;
38
+ plugins: () => Promise<TamizhiAPI.PluginInfo[]>;
39
+ setPlugin: (name: string, enabled: boolean) => Promise<TamizhiAPI.PluginInfo[]>;
40
+ run: (id: string, signal?: AbortSignal) => Promise<TamizhiAPI.RunInfo>;
41
+ cancel: (id: string) => Promise<void>;
42
+ replay: (id: string, after?: number, signal?: AbortSignal) => AsyncIterable<StreamEvent>;
43
+ }
44
+ export interface ClientOptions {
45
+ baseURL: string;
46
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
47
+ credentials?: RequestCredentials;
48
+ fetch?: typeof globalThis.fetch;
49
+ }
50
+ export declare const createTamizhiClient: (options: ClientOptions) => TamizhiClient;
51
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,185 @@
1
+ import { readEventStream } from "./stream.js";
2
+ import { approvalDecisionSchema, modelInfoSchema, runRequestSchema, storedConversationSchema } from "@vezham/tamizhi-contracts";
3
+ //#region src/lib/client.ts
4
+ var createTamizhiClient = (options) => {
5
+ const providerListeners = /* @__PURE__ */ new Set();
6
+ const changed = () => {
7
+ for (const listener of providerListeners) listener();
8
+ };
9
+ const base = options.baseURL.replace(/\/$/, "");
10
+ const fetchRequest = async (path, init) => {
11
+ const headers = new Headers(typeof options.headers === "function" ? await options.headers() : options.headers);
12
+ headers.set("Accept", "application/json");
13
+ if (typeof init.body === "string") headers.set("Content-Type", "application/json");
14
+ if (path === "/chat" || path.includes("/events")) headers.set("Accept", "text/event-stream");
15
+ const response = await (options.fetch ?? globalThis.fetch)(`${base}${path}`, {
16
+ ...init,
17
+ headers,
18
+ credentials: options.credentials ?? "same-origin"
19
+ });
20
+ if (!response.ok) {
21
+ const details = await response.json().catch(() => null);
22
+ const message = details && typeof details === "object" && "message" in details && typeof details.message === "string" ? details.message.slice(0, 500) : "";
23
+ throw new Error(`Tamizhi request failed (${response.status})${message ? `: ${message}` : ""}`);
24
+ }
25
+ return response;
26
+ };
27
+ const json = async (path, init = {}) => (await fetchRequest(path, init)).json();
28
+ const body = (value, method = "POST") => ({
29
+ method,
30
+ body: JSON.stringify(value)
31
+ });
32
+ const segment = encodeURIComponent;
33
+ const sessionPath = (id) => `/conversations/${segment(id)}`;
34
+ const upload = async (path, file, signal) => {
35
+ const data = new FormData();
36
+ data.set("file", file);
37
+ return json(path, {
38
+ method: "POST",
39
+ body: data,
40
+ signal
41
+ });
42
+ };
43
+ const replay = async function* (id, after = 0, signal) {
44
+ const response = await fetchRequest(`/runs/${segment(id)}/events?after=${after}`, { signal });
45
+ if (!response.body) throw new Error("Expected a Tamizhi event stream");
46
+ yield* readEventStream(response.body, signal);
47
+ };
48
+ return {
49
+ subscribeProviderChanges: (listener) => {
50
+ providerListeners.add(listener);
51
+ return () => {
52
+ providerListeners.delete(listener);
53
+ };
54
+ },
55
+ api: {
56
+ authorizeProvider: (id, projectId, returnTo) => json(`/providers/${segment(id)}/oauth`, body({
57
+ projectId,
58
+ returnTo
59
+ })),
60
+ providers: (signal) => json("/providers", { signal }),
61
+ connectProvider: async (id, apiKey, signal) => {
62
+ const result = await json(`/providers/${segment(id)}`, {
63
+ ...body({ apiKey }, "PUT"),
64
+ signal
65
+ });
66
+ changed();
67
+ return result;
68
+ },
69
+ setProviderSearch: async (id, enabled) => {
70
+ const result = await json(`/providers/${segment(id)}/search`, body({ enabled }, "PUT"));
71
+ changed();
72
+ return result;
73
+ },
74
+ removeProvider: async (id) => {
75
+ await fetchRequest(`/providers/${segment(id)}`, { method: "DELETE" });
76
+ changed();
77
+ },
78
+ capabilities: (signal) => json("/capabilities", { signal }),
79
+ session: (signal) => json("/session", { signal }),
80
+ authSession: (signal) => json("/auth/session", { signal }),
81
+ getLoginURL: (returnTo, connection) => {
82
+ const query = new URLSearchParams();
83
+ if (returnTo) query.set("returnTo", returnTo);
84
+ if (connection) query.set("connection", connection);
85
+ return `${base}/auth/login${query.size ? `?${query}` : ""}`;
86
+ },
87
+ loginURL: `${base}/auth/login`,
88
+ logout: async () => {
89
+ await fetchRequest("/auth/logout", { method: "POST" });
90
+ },
91
+ conversations: (query = "", cursor = "", signal) => json(`/conversations?q=${segment(query)}&cursor=${segment(cursor)}`, { signal }),
92
+ rename: (id, title) => json(sessionPath(id), body({ title }, "PATCH")),
93
+ deleteConversation: async (id) => {
94
+ await fetchRequest(sessionPath(id), { method: "DELETE" });
95
+ },
96
+ feedback: (id, message, rating) => json(`${sessionPath(id)}/messages/${segment(message)}/feedback`, body({ rating }, "PUT")),
97
+ tasks: (id, message, tasks) => json(`${sessionPath(id)}/messages/${segment(message)}/tasks`, body({ tasks }, "PUT")),
98
+ branch: (id, messageId) => json(`${sessionPath(id)}/branches`, body({ messageId })),
99
+ checkpoints: (id) => json(`${sessionPath(id)}/checkpoints`),
100
+ checkpoint: (id, label) => json(`${sessionPath(id)}/checkpoints`, body({ label })),
101
+ restore: (id, checkpoint) => json(`${sessionPath(id)}/checkpoints/${segment(checkpoint)}/restore`, { method: "POST" }),
102
+ compact: (id) => json(`${sessionPath(id)}/compact`, { method: "POST" }),
103
+ upload: (file, signal) => upload("/attachments", file, signal),
104
+ transcribe: (file, signal) => upload("/audio/transcriptions", file, signal),
105
+ speech: async (text, signal) => (await fetchRequest("/audio/speech", {
106
+ ...body({ text }),
107
+ signal
108
+ })).blob(),
109
+ memory: (query = "") => json(`/memory?q=${segment(query)}`),
110
+ remember: (text, tags) => json("/memory", body({
111
+ text,
112
+ tags
113
+ })),
114
+ forget: async (id) => {
115
+ await fetchRequest(`/memory/${segment(id)}`, { method: "DELETE" });
116
+ },
117
+ plugins: () => json("/plugins"),
118
+ setPlugin: async (name, enabled) => {
119
+ const installed = await json(`/plugins/${segment(name)}`, body({ enabled }, "PATCH"));
120
+ changed();
121
+ return installed;
122
+ },
123
+ run: (id, signal) => json(`/runs/${segment(id)}`, { signal }),
124
+ cancel: async (id) => {
125
+ await fetchRequest(`/runs/${segment(id)}/cancel`, { method: "POST" });
126
+ },
127
+ replay
128
+ },
129
+ stream: async function* (request, signal) {
130
+ const input = runRequestSchema.parse(request);
131
+ const response = await fetchRequest("/chat", {
132
+ method: "POST",
133
+ body: JSON.stringify(input),
134
+ signal
135
+ });
136
+ if (!response.headers.get("Content-Type")?.includes("text/event-stream") || !response.body) throw new Error("Expected a Tamizhi event stream");
137
+ let runId = response.headers.get("X-Tamizhi-Run-ID") ?? "";
138
+ let sequence = 0;
139
+ let current = response;
140
+ for (let attempt = 0;; attempt++) {
141
+ let terminal = false;
142
+ try {
143
+ if (!current.body) throw new Error("Expected a Tamizhi event stream");
144
+ for await (const event of readEventStream(current.body, signal)) {
145
+ runId = event.runId;
146
+ if (event.sequence !== void 0 && event.sequence <= sequence) continue;
147
+ sequence = event.sequence ?? sequence;
148
+ terminal ||= event.type === "finish" || event.type === "error";
149
+ yield event;
150
+ }
151
+ return;
152
+ } catch (error) {
153
+ if (signal?.aborted || terminal || !runId || attempt >= 3) throw error;
154
+ await new Promise((resolve, reject) => {
155
+ const onAbort = () => {
156
+ clearTimeout(timer);
157
+ reject(signal?.reason);
158
+ };
159
+ const timer = setTimeout(() => {
160
+ signal?.removeEventListener("abort", onAbort);
161
+ resolve();
162
+ }, Math.min(500 * 2 ** attempt, 4e3));
163
+ signal?.addEventListener("abort", onAbort, { once: true });
164
+ });
165
+ current = await fetchRequest(`/runs/${segment(runId)}/events?after=${sequence}`, { signal });
166
+ }
167
+ }
168
+ },
169
+ conversation: async (id, signal) => storedConversationSchema.parse(await (await fetchRequest(`/conversations/${encodeURIComponent(id)}`, { signal })).json()),
170
+ models: async (signal) => {
171
+ const data = await (await fetchRequest("/models", { signal })).json();
172
+ if (!Array.isArray(data)) throw new Error("Invalid model catalog");
173
+ return data.map((value) => modelInfoSchema.parse(value));
174
+ },
175
+ approve: async (decision, signal) => {
176
+ await fetchRequest("/approvals", {
177
+ method: "POST",
178
+ body: JSON.stringify(approvalDecisionSchema.parse(decision)),
179
+ signal
180
+ });
181
+ }
182
+ };
183
+ };
184
+ //#endregion
185
+ export { createTamizhiClient };
@@ -0,0 +1,9 @@
1
+ import type { TamizhiAPI } from '@vezham/tamizhi-contracts';
2
+ export type CompanionConnectionPrompt = Pick<TamizhiAPI.CompanionConnection, 'code' | 'expiresAt'>;
3
+ export declare const connectCompanion: (options: {
4
+ baseURL: string;
5
+ signal?: AbortSignal;
6
+ fetch?: typeof globalThis.fetch;
7
+ onPending?: (prompt: CompanionConnectionPrompt) => void;
8
+ }) => Promise<import("./client").TamizhiClient>;
9
+ //# sourceMappingURL=companion-connect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"companion-connect.d.ts","sourceRoot":"","sources":["../../src/lib/companion-connect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAK3D,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,UAAU,CAAC,mBAAmB,EAC9B,MAAM,GAAG,WAAW,CACrB,CAAA;AA4BD,eAAO,MAAM,gBAAgB,GAAU,SAAS;IAC9C,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IAC/B,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,yBAAyB,KAAK,IAAI,CAAA;CACxD,8CA8FA,CAAA"}
@@ -0,0 +1,109 @@
1
+ import { createTamizhiClient } from "./client.js";
2
+ import { companionURL } from "./companion.js";
3
+ import { z } from "zod";
4
+ //#region src/lib/companion-connect.ts
5
+ var connectionSchema = z.object({
6
+ requestId: z.string().min(32).max(128),
7
+ code: z.string().min(6).max(12),
8
+ expiresAt: z.string().datetime()
9
+ });
10
+ var resultSchema = z.object({
11
+ status: z.enum([
12
+ "pending",
13
+ "approved",
14
+ "rejected"
15
+ ]),
16
+ session: z.object({
17
+ token: z.string().min(32).max(128),
18
+ expiresAt: z.string().datetime()
19
+ }).optional()
20
+ });
21
+ var waitForPoll = (signal) => new Promise((resolve, reject) => {
22
+ const abort = () => {
23
+ clearTimeout(timer);
24
+ reject(signal.reason);
25
+ };
26
+ const timer = setTimeout(() => {
27
+ signal.removeEventListener("abort", abort);
28
+ resolve();
29
+ }, 750);
30
+ if (signal.aborted) abort();
31
+ else signal.addEventListener("abort", abort, { once: true });
32
+ });
33
+ var connectCompanion = async (options) => {
34
+ const base = companionURL(options.baseURL);
35
+ const fetcher = options.fetch ?? globalThis.fetch;
36
+ const controller = new AbortController();
37
+ const abort = () => controller.abort(options.signal?.reason);
38
+ if (options.signal?.aborted) abort();
39
+ else options.signal?.addEventListener("abort", abort, { once: true });
40
+ const timeout = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("Connection request expired. Click Connect to try again.")), 300 * 1e3);
41
+ let requestId;
42
+ let complete = false;
43
+ let notified = false;
44
+ const post = async (path, body) => {
45
+ controller.signal.throwIfAborted();
46
+ let response;
47
+ try {
48
+ response = await fetcher(base + "/auth/connect" + path, {
49
+ method: "POST",
50
+ credentials: "omit",
51
+ headers: { "Content-Type": "application/json" },
52
+ body: JSON.stringify(body),
53
+ signal: controller.signal
54
+ });
55
+ } catch (error) {
56
+ if (controller.signal.aborted) throw controller.signal.reason;
57
+ throw new Error("Cannot reach local Tamizhi. Start the companion and allow local network access in your browser.", { cause: error });
58
+ }
59
+ if (!response.ok) throw new Error(response.status === 409 ? "Another connection request is pending or the companion is busy. Try again shortly." : "Connection request expired or was refused by the companion. Click Connect to try again.");
60
+ return response.json();
61
+ };
62
+ try {
63
+ const connection = connectionSchema.parse(await post("", {}));
64
+ requestId = connection.requestId;
65
+ if (Date.parse(connection.expiresAt) <= Date.now()) throw new Error("Connection request expired. Click Connect to try again.");
66
+ while (true) {
67
+ const result = resultSchema.parse(await post("/poll", { requestId }));
68
+ if (result.status === "rejected") {
69
+ complete = true;
70
+ throw new Error("Connection rejected in the Tamizhi CLI.");
71
+ }
72
+ if (result.status === "approved") {
73
+ if (!result.session) throw new Error("The companion returned an invalid session.");
74
+ complete = true;
75
+ return createTamizhiClient({
76
+ baseURL: base,
77
+ headers: { Authorization: "Bearer " + result.session.token },
78
+ credentials: "omit",
79
+ fetch: options.fetch
80
+ });
81
+ }
82
+ if (!notified) {
83
+ notified = true;
84
+ options.onPending?.({
85
+ code: connection.code,
86
+ expiresAt: connection.expiresAt
87
+ });
88
+ }
89
+ await waitForPoll(controller.signal);
90
+ }
91
+ } finally {
92
+ clearTimeout(timeout);
93
+ options.signal?.removeEventListener("abort", abort);
94
+ if (requestId && !complete) {
95
+ const cleanup = new AbortController();
96
+ const cleanupTimeout = setTimeout(() => cleanup.abort(), 3e3);
97
+ fetcher(base + "/auth/connect/cancel", {
98
+ method: "POST",
99
+ credentials: "omit",
100
+ headers: { "Content-Type": "application/json" },
101
+ body: JSON.stringify({ requestId }),
102
+ signal: cleanup.signal,
103
+ keepalive: true
104
+ }).catch(() => {}).finally(() => clearTimeout(cleanupTimeout));
105
+ }
106
+ }
107
+ };
108
+ //#endregion
109
+ export { connectCompanion };
@@ -0,0 +1,10 @@
1
+ interface Options {
2
+ baseURL: string;
3
+ origin: string;
4
+ isSecureContext: boolean;
5
+ signal?: AbortSignal;
6
+ fetch?: typeof globalThis.fetch;
7
+ }
8
+ export declare const companionTransport: (options: Options) => Promise<typeof globalThis.fetch>;
9
+ export {};
10
+ //# sourceMappingURL=companion-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"companion-transport.d.ts","sourceRoot":"","sources":["../../src/lib/companion-transport.ts"],"names":[],"mappings":"AAIA,UAAU,OAAO;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,EAAE,OAAO,CAAA;IACxB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAChC;AAED,eAAO,MAAM,kBAAkB,GAC7B,SAAS,OAAO,KACf,OAAO,CAAC,OAAO,UAAU,CAAC,KAAK,CAgEjC,CAAA"}
@@ -0,0 +1,36 @@
1
+ import { companionURL } from "./companion.js";
2
+ //#region src/lib/companion-transport.ts
3
+ var INFO = "/__tamizhi/companion-info";
4
+ var PREFIX = "/__tamizhi/companion";
5
+ var companionTransport = async (options) => {
6
+ const fetcher = options.fetch ?? globalThis.fetch;
7
+ const base = companionURL(options.baseURL);
8
+ if (options.isSecureContext) return fetcher;
9
+ const app = new URL(options.origin);
10
+ const developmentHost = app.hostname.endsWith(".vezham.local") || [
11
+ "localhost",
12
+ "127.0.0.1",
13
+ "[::1]"
14
+ ].includes(app.hostname);
15
+ if (app.protocol === "http:" && developmentHost && base === "http://127.0.0.1:7331/api/tamizhi") try {
16
+ const response = await fetcher(app.origin + INFO, {
17
+ credentials: "omit",
18
+ signal: options.signal,
19
+ headers: { Accept: "application/json" }
20
+ });
21
+ if (response.ok && response.headers.get("Content-Type")?.includes("application/json")) {
22
+ const info = await response.json();
23
+ if (info && typeof info === "object" && "protocol" in info && info.protocol === "tamizhi-dev-proxy/v1" && "path" in info && info.path === PREFIX && "target" in info && info.target === base) return (input, init) => {
24
+ const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
25
+ if (url.href !== base && !url.href.startsWith(base + "/")) throw new Error("Development transport only supports its configured companion.");
26
+ const destination = app.origin + PREFIX + url.href.slice(base.length);
27
+ return fetcher(input instanceof Request ? new Request(destination, input) : destination, init);
28
+ };
29
+ }
30
+ } catch (error) {
31
+ if (options.signal?.aborted) throw error;
32
+ }
33
+ throw new Error("This HTTP origin cannot connect directly to the companion. For a .vezham.local Vite app, restart the dev server to enable its local relay. Otherwise use HTTPS or localhost.");
34
+ };
35
+ //#endregion
36
+ export { companionTransport };
@@ -0,0 +1,3 @@
1
+ export declare const DEFAULT_COMPANION_URL = "http://127.0.0.1:7331/api/tamizhi";
2
+ export declare const companionURL: (value: string) => string;
3
+ //# sourceMappingURL=companion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"companion.d.ts","sourceRoot":"","sources":["../../src/lib/companion.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,sCAAsC,CAAA;AACxE,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WAazC,CAAA"}
@@ -0,0 +1,13 @@
1
+ //#region src/lib/companion.ts
2
+ var DEFAULT_COMPANION_URL = "http://127.0.0.1:7331/api/tamizhi";
3
+ var companionURL = (value) => {
4
+ const url = new URL(value);
5
+ if (url.protocol !== "http:" || ![
6
+ "127.0.0.1",
7
+ "localhost",
8
+ "[::1]"
9
+ ].includes(url.hostname) || url.username || url.password || url.search || url.hash || url.pathname.replace(/\/$/, "") !== "/api/tamizhi") throw new Error("Use a loopback companion URL ending in /api/tamizhi.");
10
+ return url.href.replace(/\/$/, "");
11
+ };
12
+ //#endregion
13
+ export { DEFAULT_COMPANION_URL, companionURL };
@@ -0,0 +1,17 @@
1
+ import type { ConnectConnectionAdapter } from '@tanstack/ai-client';
2
+ import type { RunRequest, StreamEvent } from '@vezham/tamizhi-contracts';
3
+ import type { TamizhiClient } from './client';
4
+ interface ConnectionOptions {
5
+ request?: RunRequest;
6
+ permission?: RunRequest['permission'];
7
+ model?: string;
8
+ fast?: boolean;
9
+ onEvent?: (event: StreamEvent) => void | Promise<void>;
10
+ }
11
+ export declare const createTamizhiConnection: (client: TamizhiClient, options?: ConnectionOptions) => ConnectConnectionAdapter;
12
+ export declare const runTamizhiChat: (client: TamizhiClient, request: RunRequest, callbacks: {
13
+ onText: (text: string) => void;
14
+ onEvent: (event: StreamEvent) => void | Promise<void>;
15
+ }, signal?: AbortSignal) => Promise<void>;
16
+ export {};
17
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/lib/connection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAa,MAAM,qBAAqB,CAAA;AAG9E,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAGxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE7C,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,UAAU,CAAA;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC,YAAY,CAAC,CAAA;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACvD;AAYD,eAAO,MAAM,uBAAuB,GAClC,QAAQ,aAAa,EACrB,UAAS,iBAAsB,KAC9B,wBAkFD,CAAA;AAGF,eAAO,MAAM,cAAc,GACzB,QAAQ,aAAa,EACrB,SAAS,UAAU,EACnB,WAAW;IACT,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,OAAO,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACtD,EACD,SAAS,WAAW,kBAyCrB,CAAA"}
@@ -0,0 +1,125 @@
1
+ import { runRequestSchema } from "@vezham/tamizhi-contracts";
2
+ import { ChatClient } from "@tanstack/ai-client";
3
+ import { EventType } from "@tanstack/ai/client";
4
+ //#region src/lib/connection.ts
5
+ var inputText = (message) => {
6
+ if ("parts" in message) return message.parts.filter((part) => part.type === "text").map((part) => part.type === "text" ? part.content : "").join("");
7
+ return typeof message.content === "string" ? message.content : JSON.stringify(message.content);
8
+ };
9
+ var createTamizhiConnection = (client, options = {}) => ({ async *connect(messages, _data, signal, context) {
10
+ const request = options.request ?? runRequestSchema.parse({
11
+ conversationId: context.threadId,
12
+ messages: messages.filter((message) => message.role === "user" || message.role === "assistant").map((message) => ({
13
+ id: message.id,
14
+ role: message.role,
15
+ content: inputText(message)
16
+ })),
17
+ permission: options.permission ?? "ask",
18
+ model: options.model,
19
+ fast: options.fast ?? false
20
+ });
21
+ let messageId = "";
22
+ let textStarted = false;
23
+ for await (const event of client.stream(request, signal)) {
24
+ signal?.throwIfAborted();
25
+ await options.onEvent?.(event);
26
+ if (event.type === "start") yield {
27
+ type: EventType.RUN_STARTED,
28
+ threadId: event.conversationId,
29
+ runId: event.runId
30
+ };
31
+ else if (event.type === "text") {
32
+ if (!textStarted) {
33
+ messageId = event.runId + ":assistant";
34
+ textStarted = true;
35
+ yield {
36
+ type: EventType.TEXT_MESSAGE_START,
37
+ messageId,
38
+ role: "assistant"
39
+ };
40
+ }
41
+ yield {
42
+ type: EventType.TEXT_MESSAGE_CONTENT,
43
+ messageId,
44
+ delta: event.delta
45
+ };
46
+ } else if (event.type === "tool") {
47
+ yield {
48
+ type: EventType.TOOL_CALL_START,
49
+ toolCallId: event.callId,
50
+ toolCallName: event.name
51
+ };
52
+ yield {
53
+ type: EventType.TOOL_CALL_ARGS,
54
+ toolCallId: event.callId,
55
+ delta: JSON.stringify(event.input)
56
+ };
57
+ yield {
58
+ type: EventType.TOOL_CALL_END,
59
+ toolCallId: event.callId
60
+ };
61
+ } else if (event.type === "tool-result") yield {
62
+ type: EventType.TOOL_CALL_RESULT,
63
+ toolCallId: event.callId,
64
+ messageId: event.callId + ":result",
65
+ content: JSON.stringify(event.output),
66
+ role: "tool"
67
+ };
68
+ else if (event.type === "finish") {
69
+ if (textStarted) yield {
70
+ type: EventType.TEXT_MESSAGE_END,
71
+ messageId
72
+ };
73
+ yield {
74
+ type: EventType.RUN_FINISHED,
75
+ threadId: event.conversationId,
76
+ runId: event.runId
77
+ };
78
+ } else if (event.type === "error") yield {
79
+ type: EventType.RUN_ERROR,
80
+ message: event.message
81
+ };
82
+ else yield {
83
+ type: EventType.CUSTOM,
84
+ name: "tamizhi",
85
+ value: event
86
+ };
87
+ }
88
+ } });
89
+ var runTamizhiChat = async (client, request, callbacks, signal) => {
90
+ let failure;
91
+ let previous = "";
92
+ const chat = new ChatClient({
93
+ threadId: request.conversationId,
94
+ connection: createTamizhiConnection(client, {
95
+ request,
96
+ onEvent: callbacks.onEvent
97
+ }),
98
+ onMessagesChange: (messages) => {
99
+ const text = [...messages].reverse().find((item) => item.role === "assistant")?.parts.filter((part) => part.type === "text").map((part) => part.type === "text" ? part.content : "").join("");
100
+ if (text !== void 0 && text !== previous) {
101
+ previous = text;
102
+ callbacks.onText(text);
103
+ }
104
+ },
105
+ onError: (error) => {
106
+ failure = error;
107
+ }
108
+ });
109
+ const abort = () => chat.stop();
110
+ if (signal?.aborted) {
111
+ chat.dispose();
112
+ signal.throwIfAborted();
113
+ }
114
+ signal?.addEventListener("abort", abort, { once: true });
115
+ try {
116
+ await chat.sendMessage(request.messages.at(-1)?.content ?? "");
117
+ signal?.throwIfAborted();
118
+ if (failure) throw failure;
119
+ } finally {
120
+ signal?.removeEventListener("abort", abort);
121
+ chat.dispose();
122
+ }
123
+ };
124
+ //#endregion
125
+ export { createTamizhiConnection, runTamizhiChat };
@@ -0,0 +1,3 @@
1
+ import type { StreamEvent } from '@vezham/tamizhi-contracts';
2
+ export declare const readEventStream: (body: ReadableStream<Uint8Array>, signal?: AbortSignal) => AsyncGenerator<StreamEvent>;
3
+ //# sourceMappingURL=stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/lib/stream.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAE5D,eAAO,MAAM,eAAe,GAC1B,MAAM,cAAc,CAAC,UAAU,CAAC,EAChC,SAAS,WAAW,KACnB,cAAc,CAAC,WAAW,CA8C5B,CAAA"}
@@ -0,0 +1,43 @@
1
+ import { streamEventSchema } from "@vezham/tamizhi-contracts";
2
+ //#region src/lib/stream.ts
3
+ var readEventStream = async function* (body, signal) {
4
+ const reader = body.getReader();
5
+ const decoder = new TextDecoder();
6
+ let buffer = "";
7
+ const abort = () => {
8
+ reader.cancel();
9
+ };
10
+ signal?.addEventListener("abort", abort, { once: true });
11
+ if (signal?.aborted) abort();
12
+ let finished = false;
13
+ try {
14
+ for (;;) {
15
+ signal?.throwIfAborted();
16
+ const { value, done } = await reader.read();
17
+ buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
18
+ if (buffer.length > 1e6) throw new Error("Stream event exceeds the maximum size");
19
+ buffer = buffer.replace(/\r\n/g, "\n");
20
+ let boundary = buffer.indexOf("\n\n");
21
+ while (boundary >= 0) {
22
+ const frame = buffer.slice(0, boundary);
23
+ buffer = buffer.slice(boundary + 2);
24
+ const data = frame.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
25
+ if (data) {
26
+ const event = streamEventSchema.parse(JSON.parse(data));
27
+ if (event.type === "finish" || event.type === "error") finished = true;
28
+ yield event;
29
+ }
30
+ boundary = buffer.indexOf("\n\n");
31
+ }
32
+ if (done) break;
33
+ }
34
+ signal?.throwIfAborted();
35
+ if (!finished) throw new Error("The response stream ended before completion");
36
+ } finally {
37
+ signal?.removeEventListener("abort", abort);
38
+ await reader.cancel().catch(() => void 0);
39
+ reader.releaseLock();
40
+ }
41
+ };
42
+ //#endregion
43
+ export { readEventStream };
package/package.json CHANGED
@@ -1,6 +1,37 @@
1
1
  {
2
2
  "name": "@vezham/tamizhi-client",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "26.3.2",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/vezham/vx-fe-pkgs.git",
7
+ "directory": "packages/tamizhi/client"
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
+ },
19
+ "main": "./dist/index.js",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "files": [
23
+ "!**/*.tsbuildinfo",
24
+ "dist",
25
+ "LICENSE"
26
+ ],
27
+ "dependencies": {
28
+ "@tanstack/ai": "1.0.0",
29
+ "@tanstack/ai-client": "1.0.0",
30
+ "@vezham/tamizhi-contracts": "26.3.2",
31
+ "zod": "4.6.7"
32
+ },
33
+ "devDependencies": {
34
+ "@playwright/test": "^1.64.1",
35
+ "vite": "8.1.6"
36
+ }
6
37
  }