@things-factory/ai-client-base 10.1.149 → 10.1.157
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/config/config.development.js +2 -68
- package/config/config.production.js +2 -20
- package/dist-server/service/agent/agent-directory.d.ts +71 -0
- package/dist-server/service/agent/agent-directory.js +129 -0
- package/dist-server/service/agent/agent-directory.js.map +1 -0
- package/dist-server/service/agent/agent-settings-save.d.ts +38 -0
- package/dist-server/service/agent/agent-settings-save.js +153 -0
- package/dist-server/service/agent/agent-settings-save.js.map +1 -0
- package/dist-server/service/agent/agent-spec.d.ts +43 -0
- package/dist-server/service/agent/agent-spec.js +83 -0
- package/dist-server/service/agent/agent-spec.js.map +1 -0
- package/dist-server/service/agent/ai-client-for.d.ts +53 -0
- package/dist-server/service/agent/ai-client-for.js +166 -0
- package/dist-server/service/agent/ai-client-for.js.map +1 -0
- package/dist-server/service/agent/ai-usage-summary.d.ts +37 -0
- package/dist-server/service/agent/ai-usage-summary.js +108 -0
- package/dist-server/service/agent/ai-usage-summary.js.map +1 -0
- package/dist-server/service/agent/ai-usage.d.ts +35 -0
- package/dist-server/service/agent/ai-usage.js +118 -0
- package/dist-server/service/agent/ai-usage.js.map +1 -0
- package/dist-server/service/agent/index.d.ts +7 -0
- package/dist-server/service/agent/index.js +13 -0
- package/dist-server/service/agent/index.js.map +1 -0
- package/dist-server/service/agent/provider-models.d.ts +12 -0
- package/dist-server/service/agent/provider-models.js +37 -0
- package/dist-server/service/agent/provider-models.js.map +1 -0
- package/dist-server/service/capability.d.ts +21 -10
- package/dist-server/service/capability.js +20 -49
- package/dist-server/service/capability.js.map +1 -1
- package/dist-server/service/factory.d.ts +7 -12
- package/dist-server/service/factory.js +0 -45
- package/dist-server/service/factory.js.map +1 -1
- package/dist-server/service/index.d.ts +3 -0
- package/dist-server/service/index.js +6 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/providers/anthropic.d.ts +2 -0
- package/dist-server/service/providers/anthropic.js +16 -0
- package/dist-server/service/providers/anthropic.js.map +1 -1
- package/dist-server/service/providers/gemini.d.ts +13 -0
- package/dist-server/service/providers/gemini.js +52 -12
- package/dist-server/service/providers/gemini.js.map +1 -1
- package/dist-server/service/providers/openai.d.ts +6 -0
- package/dist-server/service/providers/openai.js +34 -8
- package/dist-server/service/providers/openai.js.map +1 -1
- package/dist-server/service/types.d.ts +16 -2
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -2
- package/server/service/agent/agent-directory.test.ts +206 -0
- package/server/service/agent/agent-directory.ts +198 -0
- package/server/service/agent/agent-settings-save.test.ts +151 -0
- package/server/service/agent/agent-settings-save.ts +205 -0
- package/server/service/agent/agent-spec.ts +103 -0
- package/server/service/agent/ai-client-for.test.ts +141 -0
- package/server/service/agent/ai-client-for.ts +201 -0
- package/server/service/agent/ai-usage-summary.test.ts +65 -0
- package/server/service/agent/ai-usage-summary.ts +146 -0
- package/server/service/agent/ai-usage.ts +97 -0
- package/server/service/agent/index.ts +7 -0
- package/server/service/agent/one-door.test.ts +57 -0
- package/server/service/agent/provider-models.ts +43 -0
- package/server/service/capability.test.ts +26 -55
- package/server/service/capability.ts +25 -42
- package/server/service/factory.ts +4 -44
- package/server/service/index.ts +6 -1
- package/server/service/providers/anthropic.ts +17 -0
- package/server/service/providers/gemini.ts +54 -12
- package/server/service/providers/openai-models.test.ts +30 -0
- package/server/service/providers/openai.ts +34 -8
- package/server/service/types.ts +18 -2
- package/translations/en.json +13 -0
- package/translations/es.json +13 -0
- package/translations/ja.json +13 -0
- package/translations/ko.json +13 -0
- package/translations/ms.json +13 -0
- package/translations/zh.json +13 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AiUsage = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const typeorm_1 = require("typeorm");
|
|
6
|
+
const type_graphql_1 = require("type-graphql");
|
|
7
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
8
|
+
const shell_1 = require("@things-factory/shell");
|
|
9
|
+
/**
|
|
10
|
+
* One provider request - the metering boundary writes a row each time a model is called (ADR-0061 decision 6,
|
|
11
|
+
* design/ai/agent-settings.md §6).
|
|
12
|
+
*
|
|
13
|
+
* A turn that loops through tools makes several rows. The row is joined to a conversation by id only and is never
|
|
14
|
+
* copied onto a message. It is written once and never changed, so it has no updater.
|
|
15
|
+
*/
|
|
16
|
+
let AiUsage = class AiUsage {
|
|
17
|
+
};
|
|
18
|
+
exports.AiUsage = AiUsage;
|
|
19
|
+
tslib_1.__decorate([
|
|
20
|
+
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
21
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.ID, { description: 'Row id.' }),
|
|
22
|
+
tslib_1.__metadata("design:type", String)
|
|
23
|
+
], AiUsage.prototype, "id", void 0);
|
|
24
|
+
tslib_1.__decorate([
|
|
25
|
+
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
26
|
+
(0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true, description: 'The domain the call was made in.' }),
|
|
27
|
+
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
28
|
+
], AiUsage.prototype, "domain", void 0);
|
|
29
|
+
tslib_1.__decorate([
|
|
30
|
+
(0, typeorm_1.RelationId)((usage) => usage.domain),
|
|
31
|
+
tslib_1.__metadata("design:type", String)
|
|
32
|
+
], AiUsage.prototype, "domainId", void 0);
|
|
33
|
+
tslib_1.__decorate([
|
|
34
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
35
|
+
(0, type_graphql_1.Field)(type => String, { nullable: true, description: 'The agent that answered.' }),
|
|
36
|
+
tslib_1.__metadata("design:type", String)
|
|
37
|
+
], AiUsage.prototype, "agentId", void 0);
|
|
38
|
+
tslib_1.__decorate([
|
|
39
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
40
|
+
(0, type_graphql_1.Field)(type => String, { nullable: true, description: 'The domain that declared the agent - a domain above when the agent was inherited.' }),
|
|
41
|
+
tslib_1.__metadata("design:type", String)
|
|
42
|
+
], AiUsage.prototype, "agentDomainId", void 0);
|
|
43
|
+
tslib_1.__decorate([
|
|
44
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
45
|
+
(0, type_graphql_1.Field)(type => String, { nullable: true, description: 'The domain whose API key answered - whose cost the call is.' }),
|
|
46
|
+
tslib_1.__metadata("design:type", String)
|
|
47
|
+
], AiUsage.prototype, "keyDomainId", void 0);
|
|
48
|
+
tslib_1.__decorate([
|
|
49
|
+
(0, typeorm_1.Column)(),
|
|
50
|
+
(0, type_graphql_1.Field)(type => String, { description: 'openai, anthropic or gemini.' }),
|
|
51
|
+
tslib_1.__metadata("design:type", String)
|
|
52
|
+
], AiUsage.prototype, "provider", void 0);
|
|
53
|
+
tslib_1.__decorate([
|
|
54
|
+
(0, typeorm_1.Column)(),
|
|
55
|
+
(0, type_graphql_1.Field)(type => String, { description: 'The model the provider reported answering.' }),
|
|
56
|
+
tslib_1.__metadata("design:type", String)
|
|
57
|
+
], AiUsage.prototype, "model", void 0);
|
|
58
|
+
tslib_1.__decorate([
|
|
59
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
60
|
+
(0, type_graphql_1.Field)(type => String, { nullable: true, description: 'The conversation mode kind, when the call was part of a conversation.' }),
|
|
61
|
+
tslib_1.__metadata("design:type", String)
|
|
62
|
+
], AiUsage.prototype, "modeKind", void 0);
|
|
63
|
+
tslib_1.__decorate([
|
|
64
|
+
(0, typeorm_1.Column)(),
|
|
65
|
+
(0, type_graphql_1.Field)(type => String, { description: 'chat, chatWithTools, generateJSON or vision.' }),
|
|
66
|
+
tslib_1.__metadata("design:type", String)
|
|
67
|
+
], AiUsage.prototype, "operation", void 0);
|
|
68
|
+
tslib_1.__decorate([
|
|
69
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
70
|
+
(0, type_graphql_1.Field)(type => String, { nullable: true, description: 'The conversation the call belongs to, by id only.' }),
|
|
71
|
+
tslib_1.__metadata("design:type", String)
|
|
72
|
+
], AiUsage.prototype, "sessionId", void 0);
|
|
73
|
+
tslib_1.__decorate([
|
|
74
|
+
(0, typeorm_1.Column)({ type: 'int', nullable: true }),
|
|
75
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'Input tokens the provider reported.' }),
|
|
76
|
+
tslib_1.__metadata("design:type", Number)
|
|
77
|
+
], AiUsage.prototype, "inputTokens", void 0);
|
|
78
|
+
tslib_1.__decorate([
|
|
79
|
+
(0, typeorm_1.Column)({ type: 'int', nullable: true }),
|
|
80
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'Output tokens the provider reported.' }),
|
|
81
|
+
tslib_1.__metadata("design:type", Number)
|
|
82
|
+
], AiUsage.prototype, "outputTokens", void 0);
|
|
83
|
+
tslib_1.__decorate([
|
|
84
|
+
(0, typeorm_1.Column)({ type: 'int' }),
|
|
85
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'How long the request took, in milliseconds.' }),
|
|
86
|
+
tslib_1.__metadata("design:type", Number)
|
|
87
|
+
], AiUsage.prototype, "durationMs", void 0);
|
|
88
|
+
tslib_1.__decorate([
|
|
89
|
+
(0, typeorm_1.Column)(),
|
|
90
|
+
(0, type_graphql_1.Field)(type => String, { description: 'ok, or why the request failed: key-rejected, rate-limited or provider-error.' }),
|
|
91
|
+
tslib_1.__metadata("design:type", String)
|
|
92
|
+
], AiUsage.prototype, "outcome", void 0);
|
|
93
|
+
tslib_1.__decorate([
|
|
94
|
+
(0, typeorm_1.Column)(),
|
|
95
|
+
(0, type_graphql_1.Field)(type => Date, { description: 'The UTC hour the call started in.' }),
|
|
96
|
+
tslib_1.__metadata("design:type", Date)
|
|
97
|
+
], AiUsage.prototype, "hourStart", void 0);
|
|
98
|
+
tslib_1.__decorate([
|
|
99
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
100
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'The person who made the call. Empty for a call with no person, such as a webhook.' }),
|
|
101
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
102
|
+
], AiUsage.prototype, "creator", void 0);
|
|
103
|
+
tslib_1.__decorate([
|
|
104
|
+
(0, typeorm_1.RelationId)((usage) => usage.creator),
|
|
105
|
+
tslib_1.__metadata("design:type", String)
|
|
106
|
+
], AiUsage.prototype, "creatorId", void 0);
|
|
107
|
+
tslib_1.__decorate([
|
|
108
|
+
(0, typeorm_1.CreateDateColumn)(),
|
|
109
|
+
(0, type_graphql_1.Field)(type => Date, { description: 'When the call was made.' }),
|
|
110
|
+
tslib_1.__metadata("design:type", Date)
|
|
111
|
+
], AiUsage.prototype, "createdAt", void 0);
|
|
112
|
+
exports.AiUsage = AiUsage = tslib_1.__decorate([
|
|
113
|
+
(0, typeorm_1.Entity)(),
|
|
114
|
+
(0, typeorm_1.Index)('ix_ai_usage_0', (usage) => [usage.domain, usage.hourStart]),
|
|
115
|
+
(0, typeorm_1.Index)('ix_ai_usage_1', (usage) => [usage.agentDomainId, usage.hourStart]),
|
|
116
|
+
(0, type_graphql_1.ObjectType)({ description: 'One request an AI agent sent to its provider, with what it used.' })
|
|
117
|
+
], AiUsage);
|
|
118
|
+
//# sourceMappingURL=ai-usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-usage.js","sourceRoot":"","sources":["../../../server/service/agent/ai-usage.ts"],"names":[],"mappings":"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","sourcesContent":["import { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId } from 'typeorm'\nimport { Field, ID, Int, ObjectType } from 'type-graphql'\n\nimport { User } from '@things-factory/auth-base'\nimport { Domain } from '@things-factory/shell'\n\n/**\n * One provider request - the metering boundary writes a row each time a model is called (ADR-0061 decision 6,\n * design/ai/agent-settings.md §6).\n *\n * A turn that loops through tools makes several rows. The row is joined to a conversation by id only and is never\n * copied onto a message. It is written once and never changed, so it has no updater.\n */\n@Entity()\n@Index('ix_ai_usage_0', (usage: AiUsage) => [usage.domain, usage.hourStart])\n@Index('ix_ai_usage_1', (usage: AiUsage) => [usage.agentDomainId, usage.hourStart])\n@ObjectType({ description: 'One request an AI agent sent to its provider, with what it used.' })\nexport class AiUsage {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Row id.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain the call was made in.' })\n domain?: Domain\n\n @RelationId((usage: AiUsage) => usage.domain)\n domainId?: string\n\n @Column({ nullable: true })\n @Field(type => String, { nullable: true, description: 'The agent that answered.' })\n agentId?: string\n\n @Column({ nullable: true })\n @Field(type => String, { nullable: true, description: 'The domain that declared the agent - a domain above when the agent was inherited.' })\n agentDomainId?: string\n\n @Column({ nullable: true })\n @Field(type => String, { nullable: true, description: 'The domain whose API key answered - whose cost the call is.' })\n keyDomainId?: string\n\n @Column()\n @Field(type => String, { description: 'openai, anthropic or gemini.' })\n provider: string\n\n @Column()\n @Field(type => String, { description: 'The model the provider reported answering.' })\n model: string\n\n @Column({ nullable: true })\n @Field(type => String, { nullable: true, description: 'The conversation mode kind, when the call was part of a conversation.' })\n modeKind?: string\n\n @Column()\n @Field(type => String, { description: 'chat, chatWithTools, generateJSON or vision.' })\n operation: string\n\n @Column({ nullable: true })\n @Field(type => String, { nullable: true, description: 'The conversation the call belongs to, by id only.' })\n sessionId?: string\n\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'Input tokens the provider reported.' })\n inputTokens?: number\n\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'Output tokens the provider reported.' })\n outputTokens?: number\n\n @Column({ type: 'int' })\n @Field(type => Int, { description: 'How long the request took, in milliseconds.' })\n durationMs: number\n\n /** `ok`, or why it failed: key-rejected · rate-limited · provider-error. */\n @Column()\n @Field(type => String, { description: 'ok, or why the request failed: key-rejected, rate-limited or provider-error.' })\n outcome: string\n\n /**\n * The UTC hour the call started in. Usage is grouped by this column, which every database can do; days and months\n * are rolled up from it in the domain's time zone.\n */\n @Column()\n @Field(type => Date, { description: 'The UTC hour the call started in.' })\n hourStart: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The person who made the call. Empty for a call with no person, such as a webhook.' })\n creator?: User\n\n @RelationId((usage: AiUsage) => usage.creator)\n creatorId?: string\n\n @CreateDateColumn()\n @Field(type => Date, { description: 'When the call was made.' })\n createdAt: Date\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './agent-spec.js';
|
|
2
|
+
export * from './agent-directory.js';
|
|
3
|
+
export * from './ai-client-for.js';
|
|
4
|
+
export * from './provider-models.js';
|
|
5
|
+
export * from './agent-settings-save.js';
|
|
6
|
+
export * from './ai-usage-summary.js';
|
|
7
|
+
export { AiUsage } from './ai-usage.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AiUsage = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
tslib_1.__exportStar(require("./agent-spec.js"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./agent-directory.js"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./ai-client-for.js"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./provider-models.js"), exports);
|
|
9
|
+
tslib_1.__exportStar(require("./agent-settings-save.js"), exports);
|
|
10
|
+
tslib_1.__exportStar(require("./ai-usage-summary.js"), exports);
|
|
11
|
+
var ai_usage_js_1 = require("./ai-usage.js");
|
|
12
|
+
Object.defineProperty(exports, "AiUsage", { enumerable: true, get: function () { return ai_usage_js_1.AiUsage; } });
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/agent/index.ts"],"names":[],"mappings":";;;;AAAA,0DAA+B;AAC/B,+DAAoC;AACpC,6DAAkC;AAClC,+DAAoC;AACpC,mEAAwC;AACxC,gEAAqC;AACrC,6CAAuC;AAA9B,sGAAA,OAAO,OAAA","sourcesContent":["export * from './agent-spec.js'\nexport * from './agent-directory.js'\nexport * from './ai-client-for.js'\nexport * from './provider-models.js'\nexport * from './agent-settings-save.js'\nexport * from './ai-usage-summary.js'\nexport { AiUsage } from './ai-usage.js'\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ProviderModelsRequest {
|
|
2
|
+
provider: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
baseURL?: string;
|
|
5
|
+
organization?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The conversation models a key may call, read from the provider (design/ai/agent-settings.md §3-4). A model is only
|
|
9
|
+
* ever picked from this list. Reading it also proves the key: a refused key and an unreachable provider come back as
|
|
10
|
+
* reasons, never as an empty list.
|
|
11
|
+
*/
|
|
12
|
+
export declare function listProviderModels(request: ProviderModelsRequest): Promise<string[]>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listProviderModels = listProviderModels;
|
|
4
|
+
const shell_1 = require("@things-factory/shell");
|
|
5
|
+
const factory_js_1 = require("../factory.js");
|
|
6
|
+
const agent_spec_js_1 = require("./agent-spec.js");
|
|
7
|
+
const ai_client_for_js_1 = require("./ai-client-for.js");
|
|
8
|
+
/**
|
|
9
|
+
* The conversation models a key may call, read from the provider (design/ai/agent-settings.md §3-4). A model is only
|
|
10
|
+
* ever picked from this list. Reading it also proves the key: a refused key and an unreachable provider come back as
|
|
11
|
+
* reasons, never as an empty list.
|
|
12
|
+
*/
|
|
13
|
+
async function listProviderModels(request) {
|
|
14
|
+
if (!(0, agent_spec_js_1.isAiAgentProvider)(request.provider)) {
|
|
15
|
+
throw new shell_1.Refusal('ai-agent-provider-unknown', { provider: request.provider }, `"${request.provider}" is not a provider an agent can use.`);
|
|
16
|
+
}
|
|
17
|
+
if (!request.apiKey?.trim()) {
|
|
18
|
+
throw new shell_1.Refusal('ai-agent-key-required', {}, 'Enter the API key first.');
|
|
19
|
+
}
|
|
20
|
+
const client = (0, factory_js_1.createAIClient)({
|
|
21
|
+
provider: request.provider,
|
|
22
|
+
apiKey: request.apiKey.trim(),
|
|
23
|
+
...(request.baseURL && { baseURL: request.baseURL }),
|
|
24
|
+
...(request.organization && { organization: request.organization })
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
return await client.listModels();
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
/* Gemini answers a wrong key with 400 ("API key not valid"); a list request has no other input to be wrong. */
|
|
31
|
+
if ((0, ai_client_for_js_1.outcomeOf)(error) === 'key-rejected' || Number(error?.status) === 400) {
|
|
32
|
+
throw new shell_1.Refusal('ai-provider-key-rejected', { provider: request.provider }, 'The provider did not accept this API key.');
|
|
33
|
+
}
|
|
34
|
+
throw new shell_1.Refusal('ai-provider-unreachable', { provider: request.provider }, 'The provider could not be reached.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=provider-models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-models.js","sourceRoot":"","sources":["../../../server/service/agent/provider-models.ts"],"names":[],"mappings":";;AAkBA,gDAwBC;AA1CD,iDAA+C;AAE/C,8CAA8C;AAC9C,mDAAyE;AACzE,yDAA8C;AAS9C;;;;GAIG;AACI,KAAK,UAAU,kBAAkB,CAAC,OAA8B;IACrE,IAAI,CAAC,IAAA,iCAAiB,EAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,eAAO,CAAC,2BAA2B,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,EAAE,IAAI,OAAO,CAAC,QAAQ,uCAAuC,CAAC,CAAA;IAC7I,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;QAC5B,MAAM,IAAI,eAAO,CAAC,uBAAuB,EAAE,EAAE,EAAE,0BAA0B,CAAC,CAAA;IAC5E,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,2BAAc,EAAC;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAA2B;QAC7C,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE;QAC7B,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QACpD,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC;KACpE,CAAC,CAAA;IAEF,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,UAAU,EAAE,CAAA;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,+GAA+G;QAC/G,IAAI,IAAA,4BAAS,EAAC,KAAK,CAAC,KAAK,cAAc,IAAI,MAAM,CAAE,KAAa,EAAE,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;YAClF,MAAM,IAAI,eAAO,CAAC,0BAA0B,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,EAAE,2CAA2C,CAAC,CAAA;QAC5H,CAAC;QACD,MAAM,IAAI,eAAO,CAAC,yBAAyB,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,EAAE,oCAAoC,CAAC,CAAA;IACpH,CAAC;AACH,CAAC","sourcesContent":["import { Refusal } from '@things-factory/shell'\n\nimport { createAIClient } from '../factory.js'\nimport { type AiAgentProvider, isAiAgentProvider } from './agent-spec.js'\nimport { outcomeOf } from './ai-client-for.js'\n\nexport interface ProviderModelsRequest {\n provider: string\n apiKey: string\n baseURL?: string\n organization?: string\n}\n\n/**\n * The conversation models a key may call, read from the provider (design/ai/agent-settings.md §3-4). A model is only\n * ever picked from this list. Reading it also proves the key: a refused key and an unreachable provider come back as\n * reasons, never as an empty list.\n */\nexport async function listProviderModels(request: ProviderModelsRequest): Promise<string[]> {\n if (!isAiAgentProvider(request.provider)) {\n throw new Refusal('ai-agent-provider-unknown', { provider: request.provider }, `\"${request.provider}\" is not a provider an agent can use.`)\n }\n if (!request.apiKey?.trim()) {\n throw new Refusal('ai-agent-key-required', {}, 'Enter the API key first.')\n }\n\n const client = createAIClient({\n provider: request.provider as AiAgentProvider,\n apiKey: request.apiKey.trim(),\n ...(request.baseURL && { baseURL: request.baseURL }),\n ...(request.organization && { organization: request.organization })\n })\n\n try {\n return await client.listModels()\n } catch (error) {\n /* Gemini answers a wrong key with 400 (\"API key not valid\"); a list request has no other input to be wrong. */\n if (outcomeOf(error) === 'key-rejected' || Number((error as any)?.status) === 400) {\n throw new Refusal('ai-provider-key-rejected', { provider: request.provider }, 'The provider did not accept this API key.')\n }\n throw new Refusal('ai-provider-unreachable', { provider: request.provider }, 'The provider could not be reached.')\n }\n}\n"]}
|
|
@@ -1,17 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is there an AI for this domain, and what is missing — asked before anything is tried.
|
|
3
|
+
*
|
|
4
|
+
* The twin had a button that asked the assistant, and whether to show it was decided by whether a
|
|
5
|
+
* now-dead mutation had failed once. When the dock moved to the neutral door nothing called that
|
|
6
|
+
* mutation any more, so the answer was "configured" forever: the button appeared on deployments
|
|
7
|
+
* with no provider at all and failed when pressed (standard A7-a).
|
|
8
|
+
*
|
|
9
|
+
* A capability is read, not discovered by failing (ADR-0042 decision ②). It lives here because the
|
|
10
|
+
* agents do, and the answer has three states — the third is the one a screen must not fold away:
|
|
11
|
+
* **not asked yet** is not "configured". Since agents are set per domain (design/ai/agent-settings.md),
|
|
12
|
+
* so is the answer.
|
|
13
|
+
*/
|
|
14
|
+
import type { Domain } from '@things-factory/shell';
|
|
15
|
+
import { type AiAgentDirectory } from './agent/agent-directory.js';
|
|
1
16
|
export type AiCapabilityState = 'configured' | 'not-configured';
|
|
2
17
|
export interface AiCapability {
|
|
3
18
|
state: AiCapabilityState;
|
|
4
|
-
/**
|
|
19
|
+
/** The provider of the agent a request without a choice gets. */
|
|
5
20
|
provider?: string;
|
|
6
|
-
/**
|
|
21
|
+
/** That agent's model. */
|
|
7
22
|
model?: string;
|
|
8
|
-
/** Why it is not configured, as a code: no-
|
|
23
|
+
/** Why it is not configured, as a code: no-agent · no-agent-key · no-usable-agent. */
|
|
9
24
|
reason?: string;
|
|
10
25
|
}
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* Creating the client is what proves a provider adapter is loaded and the configuration parses, so
|
|
15
|
-
* this asks for it. It never throws: a deployment with no AI is a normal deployment.
|
|
16
|
-
*/
|
|
17
|
-
export declare function aiCapability(): AiCapability;
|
|
26
|
+
/** What this domain can do, as of now. It never throws: a domain with no AI is a normal domain. */
|
|
27
|
+
export declare function aiCapability(domain: Domain): Promise<AiCapability>;
|
|
28
|
+
export declare function capabilityOf(directory: Pick<AiAgentDirectory, 'entries' | 'defaultAgentId'>): AiCapability;
|
|
@@ -1,57 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.aiCapability = aiCapability;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
* A capability is read, not discovered by failing (ADR-0042 decision ②). It lives here because the
|
|
13
|
-
* provider configuration does, and the answer has three states — the third is the one a screen
|
|
14
|
-
* must not fold away: **not asked yet** is not "configured".
|
|
15
|
-
*/
|
|
16
|
-
const env_1 = require("@things-factory/env");
|
|
17
|
-
const factory_js_1 = require("./factory.js");
|
|
18
|
-
/**
|
|
19
|
-
* What this server can do, as of now.
|
|
20
|
-
*
|
|
21
|
-
* Creating the client is what proves a provider adapter is loaded and the configuration parses, so
|
|
22
|
-
* this asks for it. It never throws: a deployment with no AI is a normal deployment.
|
|
23
|
-
*/
|
|
24
|
-
function aiCapability() {
|
|
25
|
-
const cfg = env_1.config.get('aiClient', null);
|
|
26
|
-
const client = safeDefaultClient();
|
|
27
|
-
if (client) {
|
|
28
|
-
const [provider, model] = String(client.id ?? '').split(':');
|
|
29
|
-
/*
|
|
30
|
-
A configuration that has a key slot and nothing in it - `apiKey: process.env.X` with X unset - still builds a
|
|
31
|
-
client, and every request then fails at the provider. The dock offered a chat that could only answer "something
|
|
32
|
-
went wrong" (operato-figure with no .env, 2026-09-25). No key is not configured.
|
|
33
|
-
*/
|
|
34
|
-
if (cfg && 'apiKey' in cfg && !String(cfg.apiKey ?? '').trim()) {
|
|
35
|
-
return { state: 'not-configured', provider: provider || cfg.provider, model: model || cfg.model, reason: 'no-api-key' };
|
|
36
|
-
}
|
|
37
|
-
return { state: 'configured', provider: provider || undefined, model: model || undefined };
|
|
38
|
-
}
|
|
39
|
-
if (!cfg)
|
|
40
|
-
return { state: 'not-configured', reason: 'no-config' };
|
|
41
|
-
if (!cfg.provider)
|
|
42
|
-
return { state: 'not-configured', reason: 'no-provider' };
|
|
43
|
-
if (!(0, factory_js_1.listRegisteredProviders)().includes(cfg.provider)) {
|
|
44
|
-
return { state: 'not-configured', provider: cfg.provider, reason: 'unknown-provider' };
|
|
45
|
-
}
|
|
46
|
-
return { state: 'not-configured', provider: cfg.provider, model: cfg.model, reason: 'no-adapter' };
|
|
47
|
-
}
|
|
48
|
-
function safeDefaultClient() {
|
|
4
|
+
exports.capabilityOf = capabilityOf;
|
|
5
|
+
const agent_directory_js_1 = require("./agent/agent-directory.js");
|
|
6
|
+
const ai_client_for_js_1 = require("./agent/ai-client-for.js");
|
|
7
|
+
/** What this domain can do, as of now. It never throws: a domain with no AI is a normal domain. */
|
|
8
|
+
async function aiCapability(domain) {
|
|
9
|
+
if ((0, ai_client_for_js_1.isAIClientForTesting)())
|
|
10
|
+
return { state: 'configured' };
|
|
49
11
|
try {
|
|
50
|
-
return (0,
|
|
12
|
+
return capabilityOf(await (0, agent_directory_js_1.readAiAgentDirectory)(domain));
|
|
51
13
|
}
|
|
52
|
-
catch {
|
|
53
|
-
|
|
54
|
-
return
|
|
14
|
+
catch (error) {
|
|
15
|
+
console.error('[ai-client-base] could not read the AI agents of a domain:', error?.message);
|
|
16
|
+
return { state: 'not-configured', reason: 'no-agent' };
|
|
55
17
|
}
|
|
56
18
|
}
|
|
19
|
+
function capabilityOf(directory) {
|
|
20
|
+
const chosen = directory.entries.find(one => one.spec.id === directory.defaultAgentId && one.usable);
|
|
21
|
+
if (chosen)
|
|
22
|
+
return { state: 'configured', provider: chosen.spec.provider, model: chosen.spec.model };
|
|
23
|
+
const live = directory.entries.filter(one => one.spec.active && !one.turnedOff);
|
|
24
|
+
if (!live.length)
|
|
25
|
+
return { state: 'not-configured', reason: directory.entries.length ? 'no-usable-agent' : 'no-agent' };
|
|
26
|
+
return { state: 'not-configured', reason: 'no-agent-key' };
|
|
27
|
+
}
|
|
57
28
|
//# sourceMappingURL=capability.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../../server/service/capability.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../../server/service/capability.ts"],"names":[],"mappings":";;AA+BA,oCAQC;AAED,oCAOC;AAjCD,mEAAwF;AACxF,+DAA+D;AAc/D,mGAAmG;AAC5F,KAAK,UAAU,YAAY,CAAC,MAAc;IAC/C,IAAI,IAAA,uCAAoB,GAAE;QAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAA;IAC1D,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,MAAM,IAAA,yCAAoB,EAAC,MAAM,CAAC,CAAC,CAAA;IACzD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,4DAA4D,EAAG,KAAe,EAAE,OAAO,CAAC,CAAA;QACtG,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,UAAU,EAAE,CAAA;IACxD,CAAC;AACH,CAAC;AAED,SAAgB,YAAY,CAAC,SAA+D;IAC1F,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,SAAS,CAAC,cAAc,IAAI,GAAG,CAAC,MAAM,CAAC,CAAA;IACpG,IAAI,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;IAEpG,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAC/E,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,UAAU,EAAE,CAAA;IACvH,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;AAC5D,CAAC","sourcesContent":["/**\n * Is there an AI for this domain, and what is missing — asked before anything is tried.\n *\n * The twin had a button that asked the assistant, and whether to show it was decided by whether a\n * now-dead mutation had failed once. When the dock moved to the neutral door nothing called that\n * mutation any more, so the answer was \"configured\" forever: the button appeared on deployments\n * with no provider at all and failed when pressed (standard A7-a).\n *\n * A capability is read, not discovered by failing (ADR-0042 decision ②). It lives here because the\n * agents do, and the answer has three states — the third is the one a screen must not fold away:\n * **not asked yet** is not \"configured\". Since agents are set per domain (design/ai/agent-settings.md),\n * so is the answer.\n */\nimport type { Domain } from '@things-factory/shell'\n\nimport { type AiAgentDirectory, readAiAgentDirectory } from './agent/agent-directory.js'\nimport { isAIClientForTesting } from './agent/ai-client-for.js'\n\nexport type AiCapabilityState = 'configured' | 'not-configured'\n\nexport interface AiCapability {\n state: AiCapabilityState\n /** The provider of the agent a request without a choice gets. */\n provider?: string\n /** That agent's model. */\n model?: string\n /** Why it is not configured, as a code: no-agent · no-agent-key · no-usable-agent. */\n reason?: string\n}\n\n/** What this domain can do, as of now. It never throws: a domain with no AI is a normal domain. */\nexport async function aiCapability(domain: Domain): Promise<AiCapability> {\n if (isAIClientForTesting()) return { state: 'configured' }\n try {\n return capabilityOf(await readAiAgentDirectory(domain))\n } catch (error) {\n console.error('[ai-client-base] could not read the AI agents of a domain:', (error as Error)?.message)\n return { state: 'not-configured', reason: 'no-agent' }\n }\n}\n\nexport function capabilityOf(directory: Pick<AiAgentDirectory, 'entries' | 'defaultAgentId'>): AiCapability {\n const chosen = directory.entries.find(one => one.spec.id === directory.defaultAgentId && one.usable)\n if (chosen) return { state: 'configured', provider: chosen.spec.provider, model: chosen.spec.model }\n\n const live = directory.entries.filter(one => one.spec.active && !one.turnedOff)\n if (!live.length) return { state: 'not-configured', reason: directory.entries.length ? 'no-usable-agent' : 'no-agent' }\n return { state: 'not-configured', reason: 'no-agent-key' }\n}\n"]}
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider registration and client construction.
|
|
3
|
+
*
|
|
4
|
+
* Each adapter registers itself when its module loads. `createAIClient` builds a client from a provider, model and
|
|
5
|
+
* key; it is how this package's own door (aiClientFor) and the model list reach a provider. Code outside this package
|
|
6
|
+
* gets a client from aiClientFor, for a domain - there is no deployment-wide client.
|
|
7
|
+
*/
|
|
1
8
|
import type { AIClient, AIClientConfig, AIProviderFactory } from './types.js';
|
|
2
9
|
export declare function registerAIProvider(name: string, factory: AIProviderFactory): void;
|
|
3
10
|
export declare function listRegisteredProviders(): string[];
|
|
4
11
|
export declare function createAIClient(config: AIClientConfig): AIClient;
|
|
5
|
-
/**
|
|
6
|
-
* 기본 client.
|
|
7
|
-
* 1) `setDefaultAIClient()` 로 명시 설정된 게 있으면 그것
|
|
8
|
-
* 2) env config 의 `aiClient` 키가 있으면 거기서 생성
|
|
9
|
-
* 3) 없으면 undefined (호출자는 nullable 처리)
|
|
10
|
-
*
|
|
11
|
-
* 다른 모듈 (예: codelingua) 의 `openai` 키와는 의도적으로 분리. 두 키는 서로 모르고 동작.
|
|
12
|
-
*
|
|
13
|
-
* 잘못된 config (provider 누락, 등록되지 않은 provider) 는 console.warn 후 undefined.
|
|
14
|
-
*/
|
|
15
|
-
export declare function getDefaultAIClient(): AIClient | undefined;
|
|
16
|
-
export declare function setDefaultAIClient(client: AIClient | undefined): void;
|
|
@@ -3,16 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerAIProvider = registerAIProvider;
|
|
4
4
|
exports.listRegisteredProviders = listRegisteredProviders;
|
|
5
5
|
exports.createAIClient = createAIClient;
|
|
6
|
-
exports.getDefaultAIClient = getDefaultAIClient;
|
|
7
|
-
exports.setDefaultAIClient = setDefaultAIClient;
|
|
8
|
-
/**
|
|
9
|
-
* AI Provider 등록 + Client 생성.
|
|
10
|
-
*
|
|
11
|
-
* - 어댑터 패키지(또는 동일 패키지의 providers/) 가 모듈 로드 시 `registerAIProvider` 호출.
|
|
12
|
-
* - 호출자는 `createAIClient({ provider: 'anthropic', ... })` 로 인스턴스 획득.
|
|
13
|
-
* - things-factory env config 의 `aiClient` 키가 있으면 `getDefaultAIClient()` 가 자동 생성.
|
|
14
|
-
*/
|
|
15
|
-
const env_1 = require("@things-factory/env");
|
|
16
6
|
const REGISTERED_PROVIDERS = new Map();
|
|
17
7
|
function registerAIProvider(name, factory) {
|
|
18
8
|
REGISTERED_PROVIDERS.set(name, factory);
|
|
@@ -27,39 +17,4 @@ function createAIClient(config) {
|
|
|
27
17
|
}
|
|
28
18
|
return factory(config);
|
|
29
19
|
}
|
|
30
|
-
let _defaultClient;
|
|
31
|
-
/**
|
|
32
|
-
* 기본 client.
|
|
33
|
-
* 1) `setDefaultAIClient()` 로 명시 설정된 게 있으면 그것
|
|
34
|
-
* 2) env config 의 `aiClient` 키가 있으면 거기서 생성
|
|
35
|
-
* 3) 없으면 undefined (호출자는 nullable 처리)
|
|
36
|
-
*
|
|
37
|
-
* 다른 모듈 (예: codelingua) 의 `openai` 키와는 의도적으로 분리. 두 키는 서로 모르고 동작.
|
|
38
|
-
*
|
|
39
|
-
* 잘못된 config (provider 누락, 등록되지 않은 provider) 는 console.warn 후 undefined.
|
|
40
|
-
*/
|
|
41
|
-
function getDefaultAIClient() {
|
|
42
|
-
if (_defaultClient)
|
|
43
|
-
return _defaultClient;
|
|
44
|
-
const cfg = env_1.config.get('aiClient', null);
|
|
45
|
-
if (!cfg)
|
|
46
|
-
return undefined;
|
|
47
|
-
if (!cfg.provider) {
|
|
48
|
-
console.warn('[ai-client-base] config.aiClient is set but missing required "provider" field. ' +
|
|
49
|
-
'See @things-factory/ai-client-base/config/config.development.js for examples.');
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
_defaultClient = createAIClient(cfg);
|
|
54
|
-
}
|
|
55
|
-
catch (e) {
|
|
56
|
-
console.warn(`[ai-client-base] Failed to create default AI client: ${e.message}. ` +
|
|
57
|
-
`Registered providers: ${listRegisteredProviders().join(', ') || '(none)'}`);
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
return _defaultClient;
|
|
61
|
-
}
|
|
62
|
-
function setDefaultAIClient(client) {
|
|
63
|
-
_defaultClient = client;
|
|
64
|
-
}
|
|
65
20
|
//# sourceMappingURL=factory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../server/service/factory.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../server/service/factory.ts"],"names":[],"mappings":";;AAWA,gDAEC;AAED,0DAEC;AAED,wCAQC;AAlBD,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAA6B,CAAA;AAEjE,SAAgB,kBAAkB,CAAC,IAAY,EAAE,OAA0B;IACzE,oBAAoB,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AACzC,CAAC;AAED,SAAgB,uBAAuB;IACrC,OAAO,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,CAAC,CAAA;AAChD,CAAC;AAED,SAAgB,cAAc,CAAC,MAAsB;IACnD,MAAM,OAAO,GAAG,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACzD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,wBAAwB,MAAM,CAAC,QAAQ,kBAAkB,uBAAuB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE,CAC5G,CAAA;IACH,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAA;AACxB,CAAC","sourcesContent":["/**\n * Provider registration and client construction.\n *\n * Each adapter registers itself when its module loads. `createAIClient` builds a client from a provider, model and\n * key; it is how this package's own door (aiClientFor) and the model list reach a provider. Code outside this package\n * gets a client from aiClientFor, for a domain - there is no deployment-wide client.\n */\nimport type { AIClient, AIClientConfig, AIProviderFactory } from './types.js'\n\nconst REGISTERED_PROVIDERS = new Map<string, AIProviderFactory>()\n\nexport function registerAIProvider(name: string, factory: AIProviderFactory): void {\n REGISTERED_PROVIDERS.set(name, factory)\n}\n\nexport function listRegisteredProviders(): string[] {\n return Array.from(REGISTERED_PROVIDERS.keys())\n}\n\nexport function createAIClient(config: AIClientConfig): AIClient {\n const factory = REGISTERED_PROVIDERS.get(config.provider)\n if (!factory) {\n throw new Error(\n `Unknown AI provider \"${config.provider}\". Registered: ${listRegisteredProviders().join(', ') || '(none)'}`\n )\n }\n return factory(config)\n}\n"]}
|
|
@@ -8,4 +8,7 @@ export * from './assistant/index.js';
|
|
|
8
8
|
export * from './analysis-contract.js';
|
|
9
9
|
export * from './decision/index.js';
|
|
10
10
|
export * from './capability.js';
|
|
11
|
+
export * from './agent/index.js';
|
|
12
|
+
import { AiUsage } from './agent/ai-usage.js';
|
|
13
|
+
export declare const entities: (typeof AiUsage)[];
|
|
11
14
|
export * from './testing/tool-call-context.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.entities = void 0;
|
|
3
4
|
const tslib_1 = require("tslib");
|
|
4
5
|
tslib_1.__exportStar(require("./types.js"), exports);
|
|
5
6
|
tslib_1.__exportStar(require("./factory.js"), exports);
|
|
@@ -10,7 +11,11 @@ tslib_1.__exportStar(require("./agentic-loop/index.js"), exports);
|
|
|
10
11
|
tslib_1.__exportStar(require("./assistant/index.js"), exports);
|
|
11
12
|
tslib_1.__exportStar(require("./analysis-contract.js"), exports);
|
|
12
13
|
tslib_1.__exportStar(require("./decision/index.js"), exports);
|
|
13
|
-
/* 「이
|
|
14
|
+
/* 「이 사업장에 AI 가 있나」 — 시도해서 알아내지 않고 먼저 읽는다(표준 A7-a). */
|
|
14
15
|
tslib_1.__exportStar(require("./capability.js"), exports);
|
|
16
|
+
/* AI agents per domain - the one door to a model, and its usage (design/ai/agent-settings.md). */
|
|
17
|
+
tslib_1.__exportStar(require("./agent/index.js"), exports);
|
|
18
|
+
const ai_usage_js_1 = require("./agent/ai-usage.js");
|
|
19
|
+
exports.entities = [ai_usage_js_1.AiUsage];
|
|
15
20
|
tslib_1.__exportStar(require("./testing/tool-call-context.js"), exports);
|
|
16
21
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,qDAA0B;AAC1B,uDAA4B;AAC5B,+DAAoC;AACpC,2DAAgC;AAChC,mEAAwC;AACxC,kEAAuC;AACvC,+DAAoC;AACpC,iEAAsC;AACtC,8DAAmC;AACnC,sDAAsD;AACtD,0DAA+B;AAC/B,kGAAkG;AAClG,2DAAgC;AAEhC,qDAA6C;AAChC,QAAA,QAAQ,GAAG,CAAC,qBAAO,CAAC,CAAA;AACjC,yEAA8C","sourcesContent":["export * from './types.js'\nexport * from './factory.js'\nexport * from './providers/index.js'\nexport * from './utils/index.js'\nexport * from './tool-registry/index.js'\nexport * from './agentic-loop/index.js'\nexport * from './assistant/index.js'\nexport * from './analysis-contract.js'\nexport * from './decision/index.js'\n/* 「이 사업장에 AI 가 있나」 — 시도해서 알아내지 않고 먼저 읽는다(표준 A7-a). */\nexport * from './capability.js'\n/* AI agents per domain - the one door to a model, and its usage (design/ai/agent-settings.md). */\nexport * from './agent/index.js'\n\nimport { AiUsage } from './agent/ai-usage.js'\nexport const entities = [AiUsage]\nexport * from './testing/tool-call-context.js'\n"]}
|
|
@@ -10,4 +10,6 @@ export declare class AnthropicAIClient implements AIClient {
|
|
|
10
10
|
url: string;
|
|
11
11
|
}, prompt: string, options?: AIVisionOptions): Promise<string>;
|
|
12
12
|
chatWithTools(messages: AIMessage[], tools: AITool[], options?: AIToolChatOptions): Promise<AIToolChatResult>;
|
|
13
|
+
/** Every model Anthropic lists is a conversation model. */
|
|
14
|
+
listModels(): Promise<string[]>;
|
|
13
15
|
}
|
|
@@ -40,6 +40,7 @@ class AnthropicAIClient {
|
|
|
40
40
|
...(options.stopSequences && { stop_sequences: options.stopSequences }),
|
|
41
41
|
messages: conversation
|
|
42
42
|
});
|
|
43
|
+
reportUsage(response, options, options.model ?? this.defaultModel);
|
|
43
44
|
return extractResponseText(response);
|
|
44
45
|
}
|
|
45
46
|
async generateJSON(prompt, options = {}) {
|
|
@@ -86,6 +87,7 @@ class AnthropicAIClient {
|
|
|
86
87
|
tools: sdkTools,
|
|
87
88
|
...(toolChoice && { tool_choice: toolChoice })
|
|
88
89
|
});
|
|
90
|
+
reportUsage(response, options, options.model ?? this.defaultModel);
|
|
89
91
|
const text = extractResponseText(response) || undefined;
|
|
90
92
|
const toolCalls = Array.isArray(response.content)
|
|
91
93
|
? response.content
|
|
@@ -102,8 +104,22 @@ class AnthropicAIClient {
|
|
|
102
104
|
stopReason: normalizeAnthropicStopReason(response.stop_reason)
|
|
103
105
|
};
|
|
104
106
|
}
|
|
107
|
+
/** Every model Anthropic lists is a conversation model. */
|
|
108
|
+
async listModels() {
|
|
109
|
+
const ids = [];
|
|
110
|
+
for await (const model of this.sdk.models.list())
|
|
111
|
+
ids.push(model.id);
|
|
112
|
+
return ids.sort();
|
|
113
|
+
}
|
|
105
114
|
}
|
|
106
115
|
exports.AnthropicAIClient = AnthropicAIClient;
|
|
116
|
+
function reportUsage(response, options, requested) {
|
|
117
|
+
options.onUsage?.({
|
|
118
|
+
model: response?.model ?? requested,
|
|
119
|
+
inputTokens: response?.usage?.input_tokens,
|
|
120
|
+
outputTokens: response?.usage?.output_tokens
|
|
121
|
+
});
|
|
122
|
+
}
|
|
107
123
|
// ── 변환 유틸 ─────────────────────────────────────────────────────────
|
|
108
124
|
function extractTextContent(msg) {
|
|
109
125
|
if (typeof msg.content === 'string')
|