tina4-nodejs 3.13.136 → 3.13.137
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/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +10 -5
- package/packages/core/dist/index.js +10 -5
- package/packages/core/src/aiClient.ts +14 -7
- package/packages/orm/dist/index.js +10 -5
- package/packages/orm/src/migration.ts +13 -0
- package/types/core/src/aiClient.d.ts +2 -2
package/CLAUDE.md
CHANGED
|
@@ -13,13 +13,13 @@ Even if the skill text is not currently loaded, these are non-negotiable:
|
|
|
13
13
|
|
|
14
14
|
The full discipline lives in `.claude/skills/tina4-maintainer/SKILL.md`; this block is the always-on floor.
|
|
15
15
|
|
|
16
|
-
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.
|
|
16
|
+
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.137)
|
|
17
17
|
|
|
18
18
|
> This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
|
|
19
19
|
|
|
20
20
|
## What This Project Is
|
|
21
21
|
|
|
22
|
-
Tina4 for Node.js/TypeScript v3.13.
|
|
22
|
+
Tina4 for Node.js/TypeScript v3.13.137 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
|
|
23
23
|
|
|
24
24
|
The philosophy: zero ceremony, batteries included, file system as source of truth.
|
|
25
25
|
|
package/package.json
CHANGED
package/packages/cli/dist/bin.js
CHANGED
|
@@ -12388,6 +12388,10 @@ async function recordApplied(db, name, batch, passed = 1) {
|
|
|
12388
12388
|
insertCols.push("migration_id");
|
|
12389
12389
|
values.push(name);
|
|
12390
12390
|
}
|
|
12391
|
+
if (cols.has("name")) {
|
|
12392
|
+
insertCols.push("name");
|
|
12393
|
+
values.push(name);
|
|
12394
|
+
}
|
|
12391
12395
|
if (isFirebirdAdapter(db)) {
|
|
12392
12396
|
const rows = await adapterQuery(
|
|
12393
12397
|
db,
|
|
@@ -44706,13 +44710,14 @@ var init_aiClient = __esm({
|
|
|
44706
44710
|
}
|
|
44707
44711
|
static config(capability, options) {
|
|
44708
44712
|
const provider = (options.provider ?? process.env.TINA4_AI_PROVIDER ?? "local").trim().toLowerCase();
|
|
44709
|
-
if (provider !== "local" && provider !== "openai" && provider !== "anthropic") throw new AiConfigError("TINA4_AI_PROVIDER must be local, openai, or
|
|
44713
|
+
if (provider !== "local" && provider !== "openai" && provider !== "anthropic" && provider !== "gemini") throw new AiConfigError("TINA4_AI_PROVIDER must be local, openai, anthropic, or gemini");
|
|
44710
44714
|
const key = process.env.TINA4_AI_KEY || null;
|
|
44711
|
-
if ((provider === "openai" || provider === "anthropic") && !key) throw new AiConfigError(`TINA4_AI_KEY is required for the ${provider} provider`);
|
|
44715
|
+
if ((provider === "openai" || provider === "anthropic" || provider === "gemini") && !key) throw new AiConfigError(`TINA4_AI_KEY is required for the ${provider} provider`);
|
|
44712
44716
|
const defaults = {
|
|
44713
44717
|
local: ["http://localhost:11437", "llama3.2"],
|
|
44714
44718
|
openai: ["https://api.openai.com/v1", "gpt-4o-mini"],
|
|
44715
|
-
anthropic: ["https://api.anthropic.com/v1", "claude-3-5-haiku-latest"]
|
|
44719
|
+
anthropic: ["https://api.anthropic.com/v1", "claude-3-5-haiku-latest"],
|
|
44720
|
+
gemini: ["https://generativelanguage.googleapis.com/v1beta/openai", "gemini-2.5-flash"]
|
|
44716
44721
|
};
|
|
44717
44722
|
const rawUrl = capability === "embed" && process.env.TINA4_EMBED_URL ? process.env.TINA4_EMBED_URL : process.env.TINA4_AI_URL ?? defaults[provider][0];
|
|
44718
44723
|
const model = (options.model ?? process.env.TINA4_AI_MODEL ?? defaults[provider][1]).trim();
|
|
@@ -44730,7 +44735,7 @@ var init_aiClient = __esm({
|
|
|
44730
44735
|
}
|
|
44731
44736
|
if (url.protocol !== "http:" && url.protocol !== "https:") throw new AiConfigError("AI URL must be an http or https URL");
|
|
44732
44737
|
const path10 = url.pathname.replace(/\/+$/, "");
|
|
44733
|
-
if (path10 === "" || path10 === "/v1" || path10 === "/api") {
|
|
44738
|
+
if (path10 === "" || path10 === "/v1" || path10 === "/api" || path10 === "/v1beta/openai") {
|
|
44734
44739
|
const suffix = provider === "anthropic" ? "/messages" : capability === "embed" ? "/embeddings" : "/chat/completions";
|
|
44735
44740
|
url.pathname = (path10 || "/v1") + suffix;
|
|
44736
44741
|
}
|
|
@@ -44738,7 +44743,7 @@ var init_aiClient = __esm({
|
|
|
44738
44743
|
}
|
|
44739
44744
|
static headers(config) {
|
|
44740
44745
|
const headers = { "content-type": "application/json", accept: "application/json" };
|
|
44741
|
-
if (config.provider === "openai") headers.authorization = `Bearer ${config.key}`;
|
|
44746
|
+
if (config.provider === "openai" || config.provider === "gemini") headers.authorization = `Bearer ${config.key}`;
|
|
44742
44747
|
if (config.provider === "anthropic") {
|
|
44743
44748
|
headers["x-api-key"] = config.key;
|
|
44744
44749
|
headers["anthropic-version"] = "2023-06-01";
|
|
@@ -12387,6 +12387,10 @@ async function recordApplied(db, name, batch, passed = 1) {
|
|
|
12387
12387
|
insertCols.push("migration_id");
|
|
12388
12388
|
values.push(name);
|
|
12389
12389
|
}
|
|
12390
|
+
if (cols.has("name")) {
|
|
12391
|
+
insertCols.push("name");
|
|
12392
|
+
values.push(name);
|
|
12393
|
+
}
|
|
12390
12394
|
if (isFirebirdAdapter(db)) {
|
|
12391
12395
|
const rows = await adapterQuery(
|
|
12392
12396
|
db,
|
|
@@ -44667,13 +44671,14 @@ var init_aiClient = __esm({
|
|
|
44667
44671
|
}
|
|
44668
44672
|
static config(capability, options) {
|
|
44669
44673
|
const provider = (options.provider ?? process.env.TINA4_AI_PROVIDER ?? "local").trim().toLowerCase();
|
|
44670
|
-
if (provider !== "local" && provider !== "openai" && provider !== "anthropic") throw new AiConfigError("TINA4_AI_PROVIDER must be local, openai, or
|
|
44674
|
+
if (provider !== "local" && provider !== "openai" && provider !== "anthropic" && provider !== "gemini") throw new AiConfigError("TINA4_AI_PROVIDER must be local, openai, anthropic, or gemini");
|
|
44671
44675
|
const key = process.env.TINA4_AI_KEY || null;
|
|
44672
|
-
if ((provider === "openai" || provider === "anthropic") && !key) throw new AiConfigError(`TINA4_AI_KEY is required for the ${provider} provider`);
|
|
44676
|
+
if ((provider === "openai" || provider === "anthropic" || provider === "gemini") && !key) throw new AiConfigError(`TINA4_AI_KEY is required for the ${provider} provider`);
|
|
44673
44677
|
const defaults = {
|
|
44674
44678
|
local: ["http://localhost:11437", "llama3.2"],
|
|
44675
44679
|
openai: ["https://api.openai.com/v1", "gpt-4o-mini"],
|
|
44676
|
-
anthropic: ["https://api.anthropic.com/v1", "claude-3-5-haiku-latest"]
|
|
44680
|
+
anthropic: ["https://api.anthropic.com/v1", "claude-3-5-haiku-latest"],
|
|
44681
|
+
gemini: ["https://generativelanguage.googleapis.com/v1beta/openai", "gemini-2.5-flash"]
|
|
44677
44682
|
};
|
|
44678
44683
|
const rawUrl = capability === "embed" && process.env.TINA4_EMBED_URL ? process.env.TINA4_EMBED_URL : process.env.TINA4_AI_URL ?? defaults[provider][0];
|
|
44679
44684
|
const model = (options.model ?? process.env.TINA4_AI_MODEL ?? defaults[provider][1]).trim();
|
|
@@ -44691,7 +44696,7 @@ var init_aiClient = __esm({
|
|
|
44691
44696
|
}
|
|
44692
44697
|
if (url.protocol !== "http:" && url.protocol !== "https:") throw new AiConfigError("AI URL must be an http or https URL");
|
|
44693
44698
|
const path10 = url.pathname.replace(/\/+$/, "");
|
|
44694
|
-
if (path10 === "" || path10 === "/v1" || path10 === "/api") {
|
|
44699
|
+
if (path10 === "" || path10 === "/v1" || path10 === "/api" || path10 === "/v1beta/openai") {
|
|
44695
44700
|
const suffix = provider === "anthropic" ? "/messages" : capability === "embed" ? "/embeddings" : "/chat/completions";
|
|
44696
44701
|
url.pathname = (path10 || "/v1") + suffix;
|
|
44697
44702
|
}
|
|
@@ -44699,7 +44704,7 @@ var init_aiClient = __esm({
|
|
|
44699
44704
|
}
|
|
44700
44705
|
static headers(config) {
|
|
44701
44706
|
const headers = { "content-type": "application/json", accept: "application/json" };
|
|
44702
|
-
if (config.provider === "openai") headers.authorization = `Bearer ${config.key}`;
|
|
44707
|
+
if (config.provider === "openai" || config.provider === "gemini") headers.authorization = `Bearer ${config.key}`;
|
|
44703
44708
|
if (config.provider === "anthropic") {
|
|
44704
44709
|
headers["x-api-key"] = config.key;
|
|
44705
44710
|
headers["anthropic-version"] = "2023-06-01";
|
|
@@ -87,7 +87,7 @@ export interface AiChatOptions {
|
|
|
87
87
|
maxTokens?: number;
|
|
88
88
|
stream?: boolean;
|
|
89
89
|
timeout?: number;
|
|
90
|
-
provider?: "local" | "openai" | "anthropic";
|
|
90
|
+
provider?: "local" | "openai" | "anthropic" | "gemini";
|
|
91
91
|
/** Tools the model may call. ADR-0061 — translated per provider. */
|
|
92
92
|
tools?: AiToolDeclaration[];
|
|
93
93
|
/**
|
|
@@ -97,10 +97,10 @@ export interface AiChatOptions {
|
|
|
97
97
|
*/
|
|
98
98
|
toolChoice?: AiToolChoice;
|
|
99
99
|
}
|
|
100
|
-
export interface AiEmbedOptions { model?: string; timeout?: number; provider?: "local" | "openai" | "anthropic" }
|
|
100
|
+
export interface AiEmbedOptions { model?: string; timeout?: number; provider?: "local" | "openai" | "anthropic" | "gemini" }
|
|
101
101
|
|
|
102
102
|
interface Config {
|
|
103
|
-
provider: "local" | "openai" | "anthropic";
|
|
103
|
+
provider: "local" | "openai" | "anthropic" | "gemini";
|
|
104
104
|
url: string;
|
|
105
105
|
model: string;
|
|
106
106
|
key: string | null;
|
|
@@ -288,13 +288,16 @@ export class Ai {
|
|
|
288
288
|
|
|
289
289
|
private static config(capability: "chat" | "embed", options: AiChatOptions | AiEmbedOptions): Config {
|
|
290
290
|
const provider = (options.provider ?? process.env.TINA4_AI_PROVIDER ?? "local").trim().toLowerCase();
|
|
291
|
-
if (provider !== "local" && provider !== "openai" && provider !== "anthropic") throw new AiConfigError("TINA4_AI_PROVIDER must be local, openai, or
|
|
291
|
+
if (provider !== "local" && provider !== "openai" && provider !== "anthropic" && provider !== "gemini") throw new AiConfigError("TINA4_AI_PROVIDER must be local, openai, anthropic, or gemini");
|
|
292
292
|
const key = process.env.TINA4_AI_KEY || null;
|
|
293
|
-
if ((provider === "openai" || provider === "anthropic") && !key) throw new AiConfigError(`TINA4_AI_KEY is required for the ${provider} provider`);
|
|
293
|
+
if ((provider === "openai" || provider === "anthropic" || provider === "gemini") && !key) throw new AiConfigError(`TINA4_AI_KEY is required for the ${provider} provider`);
|
|
294
|
+
// Gemini speaks the OpenAI wire format at its OpenAI-compatible base, so it rides the openai
|
|
295
|
+
// body/parse/stream path - only the base URL, the endpoint suffix append, and the Bearer key differ.
|
|
294
296
|
const defaults: Record<Config["provider"], [string, string]> = {
|
|
295
297
|
local: ["http://localhost:11437", "llama3.2"],
|
|
296
298
|
openai: ["https://api.openai.com/v1", "gpt-4o-mini"],
|
|
297
299
|
anthropic: ["https://api.anthropic.com/v1", "claude-3-5-haiku-latest"],
|
|
300
|
+
gemini: ["https://generativelanguage.googleapis.com/v1beta/openai", "gemini-2.5-flash"],
|
|
298
301
|
};
|
|
299
302
|
const rawUrl = capability === "embed" && process.env.TINA4_EMBED_URL ? process.env.TINA4_EMBED_URL : (process.env.TINA4_AI_URL ?? defaults[provider][0]);
|
|
300
303
|
const model = (options.model ?? process.env.TINA4_AI_MODEL ?? defaults[provider][1]).trim();
|
|
@@ -309,7 +312,11 @@ export class Ai {
|
|
|
309
312
|
try { url = new URL(value); } catch { throw new AiConfigError("AI URL must be an http or https URL"); }
|
|
310
313
|
if (url.protocol !== "http:" && url.protocol !== "https:") throw new AiConfigError("AI URL must be an http or https URL");
|
|
311
314
|
const path = url.pathname.replace(/\/+$/, "");
|
|
312
|
-
|
|
315
|
+
// "/v1beta/openai" is Gemini's OpenAI-compatible base; append the suffix onto it exactly as
|
|
316
|
+
// onto a bare host / "/v1" / "/api", so the default gemini base resolves to
|
|
317
|
+
// .../v1beta/openai/chat/completions (or /embeddings). A full endpoint URL the caller supplies
|
|
318
|
+
// (any other path) still passes through verbatim.
|
|
319
|
+
if (path === "" || path === "/v1" || path === "/api" || path === "/v1beta/openai") {
|
|
313
320
|
const suffix = provider === "anthropic" ? "/messages" : capability === "embed" ? "/embeddings" : "/chat/completions";
|
|
314
321
|
url.pathname = (path || "/v1") + suffix;
|
|
315
322
|
}
|
|
@@ -318,7 +325,7 @@ export class Ai {
|
|
|
318
325
|
|
|
319
326
|
private static headers(config: Config): Record<string, string> {
|
|
320
327
|
const headers: Record<string, string> = { "content-type": "application/json", accept: "application/json" };
|
|
321
|
-
if (config.provider === "openai") headers.authorization = `Bearer ${config.key}`;
|
|
328
|
+
if (config.provider === "openai" || config.provider === "gemini") headers.authorization = `Bearer ${config.key}`;
|
|
322
329
|
if (config.provider === "anthropic") { headers["x-api-key"] = config.key!; headers["anthropic-version"] = "2023-06-01"; }
|
|
323
330
|
return headers;
|
|
324
331
|
}
|
|
@@ -33487,13 +33487,14 @@ var init_aiClient = __esm({
|
|
|
33487
33487
|
}
|
|
33488
33488
|
static config(capability, options) {
|
|
33489
33489
|
const provider = (options.provider ?? process.env.TINA4_AI_PROVIDER ?? "local").trim().toLowerCase();
|
|
33490
|
-
if (provider !== "local" && provider !== "openai" && provider !== "anthropic") throw new AiConfigError("TINA4_AI_PROVIDER must be local, openai, or
|
|
33490
|
+
if (provider !== "local" && provider !== "openai" && provider !== "anthropic" && provider !== "gemini") throw new AiConfigError("TINA4_AI_PROVIDER must be local, openai, anthropic, or gemini");
|
|
33491
33491
|
const key = process.env.TINA4_AI_KEY || null;
|
|
33492
|
-
if ((provider === "openai" || provider === "anthropic") && !key) throw new AiConfigError(`TINA4_AI_KEY is required for the ${provider} provider`);
|
|
33492
|
+
if ((provider === "openai" || provider === "anthropic" || provider === "gemini") && !key) throw new AiConfigError(`TINA4_AI_KEY is required for the ${provider} provider`);
|
|
33493
33493
|
const defaults = {
|
|
33494
33494
|
local: ["http://localhost:11437", "llama3.2"],
|
|
33495
33495
|
openai: ["https://api.openai.com/v1", "gpt-4o-mini"],
|
|
33496
|
-
anthropic: ["https://api.anthropic.com/v1", "claude-3-5-haiku-latest"]
|
|
33496
|
+
anthropic: ["https://api.anthropic.com/v1", "claude-3-5-haiku-latest"],
|
|
33497
|
+
gemini: ["https://generativelanguage.googleapis.com/v1beta/openai", "gemini-2.5-flash"]
|
|
33497
33498
|
};
|
|
33498
33499
|
const rawUrl = capability === "embed" && process.env.TINA4_EMBED_URL ? process.env.TINA4_EMBED_URL : process.env.TINA4_AI_URL ?? defaults[provider][0];
|
|
33499
33500
|
const model = (options.model ?? process.env.TINA4_AI_MODEL ?? defaults[provider][1]).trim();
|
|
@@ -33511,7 +33512,7 @@ var init_aiClient = __esm({
|
|
|
33511
33512
|
}
|
|
33512
33513
|
if (url.protocol !== "http:" && url.protocol !== "https:") throw new AiConfigError("AI URL must be an http or https URL");
|
|
33513
33514
|
const path10 = url.pathname.replace(/\/+$/, "");
|
|
33514
|
-
if (path10 === "" || path10 === "/v1" || path10 === "/api") {
|
|
33515
|
+
if (path10 === "" || path10 === "/v1" || path10 === "/api" || path10 === "/v1beta/openai") {
|
|
33515
33516
|
const suffix = provider === "anthropic" ? "/messages" : capability === "embed" ? "/embeddings" : "/chat/completions";
|
|
33516
33517
|
url.pathname = (path10 || "/v1") + suffix;
|
|
33517
33518
|
}
|
|
@@ -33519,7 +33520,7 @@ var init_aiClient = __esm({
|
|
|
33519
33520
|
}
|
|
33520
33521
|
static headers(config) {
|
|
33521
33522
|
const headers = { "content-type": "application/json", accept: "application/json" };
|
|
33522
|
-
if (config.provider === "openai") headers.authorization = `Bearer ${config.key}`;
|
|
33523
|
+
if (config.provider === "openai" || config.provider === "gemini") headers.authorization = `Bearer ${config.key}`;
|
|
33523
33524
|
if (config.provider === "anthropic") {
|
|
33524
33525
|
headers["x-api-key"] = config.key;
|
|
33525
33526
|
headers["anthropic-version"] = "2023-06-01";
|
|
@@ -41799,6 +41800,10 @@ async function recordApplied(db, name, batch, passed = 1) {
|
|
|
41799
41800
|
insertCols.push("migration_id");
|
|
41800
41801
|
values.push(name);
|
|
41801
41802
|
}
|
|
41803
|
+
if (cols.has("name")) {
|
|
41804
|
+
insertCols.push("name");
|
|
41805
|
+
values.push(name);
|
|
41806
|
+
}
|
|
41802
41807
|
if (isFirebirdAdapter(db)) {
|
|
41803
41808
|
const rows = await adapterQuery(
|
|
41804
41809
|
db,
|
|
@@ -524,6 +524,19 @@ async function recordApplied(
|
|
|
524
524
|
values.push(name);
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
+
// A table created by an OLDER Node v3 (<= 3.13.54) carries `name VARCHAR(500)
|
|
528
|
+
// NOT NULL` (the pre-3.13.55 tracking DDL — see git history). upgradeMigrationTable()
|
|
529
|
+
// copies its values into migration_name and leaves the `name` column in place
|
|
530
|
+
// (SQLite cannot drop a NOT NULL column without a full rebuild). Exactly like the
|
|
531
|
+
// python#93 `migration_id` case above, an insert that omits `name` fails its
|
|
532
|
+
// NOT NULL constraint, wedging EVERY migration recorded after the upgrade — so
|
|
533
|
+
// populate it here too, mirroring migration_name. A fresh canonical table has no
|
|
534
|
+
// `name` column, so this only fires on an upgraded legacy table.
|
|
535
|
+
if (cols.has("name")) {
|
|
536
|
+
insertCols.push("name");
|
|
537
|
+
values.push(name);
|
|
538
|
+
}
|
|
539
|
+
|
|
527
540
|
if (isFirebirdAdapter(db)) {
|
|
528
541
|
// Firebird: generate the id from the sequence.
|
|
529
542
|
const rows = await adapterQuery<{ next_id: number }>(db,
|
|
@@ -113,7 +113,7 @@ export interface AiChatOptions {
|
|
|
113
113
|
maxTokens?: number;
|
|
114
114
|
stream?: boolean;
|
|
115
115
|
timeout?: number;
|
|
116
|
-
provider?: "local" | "openai" | "anthropic";
|
|
116
|
+
provider?: "local" | "openai" | "anthropic" | "gemini";
|
|
117
117
|
/** Tools the model may call. ADR-0061 — translated per provider. */
|
|
118
118
|
tools?: AiToolDeclaration[];
|
|
119
119
|
/**
|
|
@@ -126,7 +126,7 @@ export interface AiChatOptions {
|
|
|
126
126
|
export interface AiEmbedOptions {
|
|
127
127
|
model?: string;
|
|
128
128
|
timeout?: number;
|
|
129
|
-
provider?: "local" | "openai" | "anthropic";
|
|
129
|
+
provider?: "local" | "openai" | "anthropic" | "gemini";
|
|
130
130
|
}
|
|
131
131
|
export declare class Ai {
|
|
132
132
|
static chat(messages: AiMessage[], options: AiChatOptions & {
|