@meuecommerce/frete-adapter-node 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/openAIBoxDistributor.js +125 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/openAIBoxDistributor.d.ts +21 -0
- package/dist/openAIBoxDistributor.js +122 -0
- package/package.json +2 -2
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createOpenAIDimensionEstimator = exports.EnvSecretStore = exports.InMemorySettingsStore = exports.createCorreiosHttpClient = void 0;
|
|
3
|
+
exports.createOpenAIBoxDistributor = exports.createOpenAIDimensionEstimator = exports.EnvSecretStore = exports.InMemorySettingsStore = exports.createCorreiosHttpClient = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* @meuecommerce/frete-adapter-node — Node implementations of @meuecommerce/frete ports.
|
|
6
6
|
*
|
|
@@ -15,3 +15,5 @@ var envSecretStore_js_1 = require("./envSecretStore.js");
|
|
|
15
15
|
Object.defineProperty(exports, "EnvSecretStore", { enumerable: true, get: function () { return envSecretStore_js_1.EnvSecretStore; } });
|
|
16
16
|
var openAIDimensionEstimator_js_1 = require("./openAIDimensionEstimator.js");
|
|
17
17
|
Object.defineProperty(exports, "createOpenAIDimensionEstimator", { enumerable: true, get: function () { return openAIDimensionEstimator_js_1.createOpenAIDimensionEstimator; } });
|
|
18
|
+
var openAIBoxDistributor_js_1 = require("./openAIBoxDistributor.js");
|
|
19
|
+
Object.defineProperty(exports, "createOpenAIBoxDistributor", { enumerable: true, get: function () { return openAIBoxDistributor_js_1.createOpenAIBoxDistributor; } });
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOpenAIBoxDistributor = createOpenAIBoxDistributor;
|
|
4
|
+
/**
|
|
5
|
+
* `ProductBoxDistributor` implemented with OpenAI — the AI that distributes
|
|
6
|
+
* products into boxes (a second call from the Velo `openAI/box-methods.js`
|
|
7
|
+
* `aiDistributeProducts`, tool `distribute_products`).
|
|
8
|
+
*
|
|
9
|
+
* Best-effort: `packBoxes` validates the output and falls back to the
|
|
10
|
+
* deterministic distribution, so a bad/failed call never breaks packing. `fetch`
|
|
11
|
+
* is injectable (Velo passes `wix-fetch`); the API key is passed in.
|
|
12
|
+
*/
|
|
13
|
+
const frete_1 = require("@meuecommerce/frete");
|
|
14
|
+
const DISTRIBUTE_TOOL = {
|
|
15
|
+
type: "function",
|
|
16
|
+
function: {
|
|
17
|
+
name: "distribute_products",
|
|
18
|
+
description: "Distribui TODOS os produtos nas quantidades EXATAS - NUNCA perca produtos ou invente nomes",
|
|
19
|
+
parameters: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
boxes: {
|
|
23
|
+
type: "array",
|
|
24
|
+
items: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
box: { type: "string", description: "ID EXATO da caixa - NUNCA invente" },
|
|
28
|
+
products: {
|
|
29
|
+
type: "array",
|
|
30
|
+
items: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: { name: { type: "string" }, quantity: { type: "number", minimum: 1 } },
|
|
33
|
+
required: ["name", "quantity"],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
dimensions: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: { width: { type: "number" }, height: { type: "number" }, length: { type: "number" } },
|
|
39
|
+
required: ["width", "height", "length"],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ["box", "products", "dimensions"],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
required: ["boxes"],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
function findByName(name, items) {
|
|
51
|
+
return items.find((i) => i.name === name);
|
|
52
|
+
}
|
|
53
|
+
function createPrompt(products, boxes, dims) {
|
|
54
|
+
const totalVolume = dims.products.reduce((total, p) => {
|
|
55
|
+
const product = findByName(p.name, products);
|
|
56
|
+
return product ? total + (0, frete_1.calculateVolume)(p.length, p.width, p.height) * product.quantity : total;
|
|
57
|
+
}, 0);
|
|
58
|
+
const boxLines = boxes
|
|
59
|
+
.map((box) => {
|
|
60
|
+
const cap = (0, frete_1.calculateVolume)(box.width, box.height, box.length);
|
|
61
|
+
const pct = Math.round((totalVolume / cap) * 100);
|
|
62
|
+
return `• ${box.id ?? box.name} (${box.name}): ${cap}cm³ — ${pct}% ${pct > 80 ? "❌ REJEITADA" : "✅ APROVADA"}`;
|
|
63
|
+
})
|
|
64
|
+
.join("\n");
|
|
65
|
+
const productLines = dims.products
|
|
66
|
+
.map((p) => {
|
|
67
|
+
const product = findByName(p.name, products);
|
|
68
|
+
if (!product)
|
|
69
|
+
return `• ${p.name}: não encontrado no carrinho`;
|
|
70
|
+
const vol = (0, frete_1.calculateVolume)(p.length, p.width, p.height);
|
|
71
|
+
return `• ${p.name}: ${product.quantity}x — ${p.length}×${p.width}×${p.height}cm — total ${vol * product.quantity}cm³`;
|
|
72
|
+
})
|
|
73
|
+
.join("\n");
|
|
74
|
+
return (`Especialista em logística: distribua TODOS os produtos nas caixas.\n\n` +
|
|
75
|
+
`REGRA: o volume dos produtos numa caixa não pode passar de 80% da capacidade dela.\n` +
|
|
76
|
+
`Volume total dos produtos: ${totalVolume}cm³\n\n` +
|
|
77
|
+
`CAIXAS:\n${boxLines}\n\nPRODUTOS:\n${productLines}\n\n` +
|
|
78
|
+
`PRIORIDADE ABSOLUTA: menor número de caixas = menor custo. Use 1 caixa quando couber; ` +
|
|
79
|
+
`só use múltiplas se for matematicamente impossível. Use IDs de caixa EXATOS e preserve os nomes e quantidades.\n` +
|
|
80
|
+
`Responda usando a função 'distribute_products'.`);
|
|
81
|
+
}
|
|
82
|
+
function createOpenAIBoxDistributor(options) {
|
|
83
|
+
const { apiKey, fetch = globalThis.fetch, model = "gpt-4o-mini", temperature = 0.5, apiUrl = "https://api.openai.com/v1/chat/completions", timeoutMs = 20000, } = options;
|
|
84
|
+
if (!apiKey)
|
|
85
|
+
throw new Error("createOpenAIBoxDistributor: apiKey is required");
|
|
86
|
+
if (typeof fetch !== "function")
|
|
87
|
+
throw new Error("createOpenAIBoxDistributor: no fetch available");
|
|
88
|
+
return {
|
|
89
|
+
async distribute(products, boxes, estimatedDimensions) {
|
|
90
|
+
const body = {
|
|
91
|
+
model,
|
|
92
|
+
messages: [
|
|
93
|
+
{ role: "system", content: "Você é um especialista em otimização logística. SEMPRE maximize consolidação e minimize número de caixas." },
|
|
94
|
+
{ role: "user", content: createPrompt(products, boxes, estimatedDimensions) },
|
|
95
|
+
],
|
|
96
|
+
tools: [DISTRIBUTE_TOOL],
|
|
97
|
+
tool_choice: { type: "function", function: { name: "distribute_products" } },
|
|
98
|
+
temperature,
|
|
99
|
+
};
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
102
|
+
let response;
|
|
103
|
+
try {
|
|
104
|
+
response = await fetch(apiUrl, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
107
|
+
body: JSON.stringify(body),
|
|
108
|
+
signal: controller.signal,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
}
|
|
114
|
+
if (!response.ok)
|
|
115
|
+
throw new Error(`OpenAI API Error: ${response.status} ${response.statusText}`);
|
|
116
|
+
const json = (await response.json());
|
|
117
|
+
if (json.error)
|
|
118
|
+
throw new Error(`OpenAI Error: ${json.error.message}`);
|
|
119
|
+
const args = json.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments;
|
|
120
|
+
if (!args)
|
|
121
|
+
throw new Error("IA não conseguiu distribuir os produtos");
|
|
122
|
+
return JSON.parse(args);
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,3 +12,5 @@ export { EnvSecretStore } from "./envSecretStore.js";
|
|
|
12
12
|
export type { EnvSecretStoreOptions } from "./envSecretStore.js";
|
|
13
13
|
export { createOpenAIDimensionEstimator } from "./openAIDimensionEstimator.js";
|
|
14
14
|
export type { OpenAIDimensionEstimatorOptions } from "./openAIDimensionEstimator.js";
|
|
15
|
+
export { createOpenAIBoxDistributor } from "./openAIBoxDistributor.js";
|
|
16
|
+
export type { OpenAIBoxDistributorOptions } from "./openAIBoxDistributor.js";
|
package/dist/index.js
CHANGED
|
@@ -8,3 +8,4 @@ export { createCorreiosHttpClient } from "./correiosHttpClient.js";
|
|
|
8
8
|
export { InMemorySettingsStore } from "./inMemorySettingsStore.js";
|
|
9
9
|
export { EnvSecretStore } from "./envSecretStore.js";
|
|
10
10
|
export { createOpenAIDimensionEstimator } from "./openAIDimensionEstimator.js";
|
|
11
|
+
export { createOpenAIBoxDistributor } from "./openAIBoxDistributor.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ProductBoxDistributor` implemented with OpenAI — the AI that distributes
|
|
3
|
+
* products into boxes (a second call from the Velo `openAI/box-methods.js`
|
|
4
|
+
* `aiDistributeProducts`, tool `distribute_products`).
|
|
5
|
+
*
|
|
6
|
+
* Best-effort: `packBoxes` validates the output and falls back to the
|
|
7
|
+
* deterministic distribution, so a bad/failed call never breaks packing. `fetch`
|
|
8
|
+
* is injectable (Velo passes `wix-fetch`); the API key is passed in.
|
|
9
|
+
*/
|
|
10
|
+
import { type ProductBoxDistributor } from "@meuecommerce/frete";
|
|
11
|
+
type FetchLike = typeof globalThis.fetch;
|
|
12
|
+
export interface OpenAIBoxDistributorOptions {
|
|
13
|
+
apiKey: string;
|
|
14
|
+
fetch?: FetchLike;
|
|
15
|
+
model?: string;
|
|
16
|
+
temperature?: number;
|
|
17
|
+
apiUrl?: string;
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function createOpenAIBoxDistributor(options: OpenAIBoxDistributorOptions): ProductBoxDistributor;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ProductBoxDistributor` implemented with OpenAI — the AI that distributes
|
|
3
|
+
* products into boxes (a second call from the Velo `openAI/box-methods.js`
|
|
4
|
+
* `aiDistributeProducts`, tool `distribute_products`).
|
|
5
|
+
*
|
|
6
|
+
* Best-effort: `packBoxes` validates the output and falls back to the
|
|
7
|
+
* deterministic distribution, so a bad/failed call never breaks packing. `fetch`
|
|
8
|
+
* is injectable (Velo passes `wix-fetch`); the API key is passed in.
|
|
9
|
+
*/
|
|
10
|
+
import { calculateVolume, } from "@meuecommerce/frete";
|
|
11
|
+
const DISTRIBUTE_TOOL = {
|
|
12
|
+
type: "function",
|
|
13
|
+
function: {
|
|
14
|
+
name: "distribute_products",
|
|
15
|
+
description: "Distribui TODOS os produtos nas quantidades EXATAS - NUNCA perca produtos ou invente nomes",
|
|
16
|
+
parameters: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
boxes: {
|
|
20
|
+
type: "array",
|
|
21
|
+
items: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
box: { type: "string", description: "ID EXATO da caixa - NUNCA invente" },
|
|
25
|
+
products: {
|
|
26
|
+
type: "array",
|
|
27
|
+
items: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: { name: { type: "string" }, quantity: { type: "number", minimum: 1 } },
|
|
30
|
+
required: ["name", "quantity"],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
dimensions: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: { width: { type: "number" }, height: { type: "number" }, length: { type: "number" } },
|
|
36
|
+
required: ["width", "height", "length"],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ["box", "products", "dimensions"],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
required: ["boxes"],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
function findByName(name, items) {
|
|
48
|
+
return items.find((i) => i.name === name);
|
|
49
|
+
}
|
|
50
|
+
function createPrompt(products, boxes, dims) {
|
|
51
|
+
const totalVolume = dims.products.reduce((total, p) => {
|
|
52
|
+
const product = findByName(p.name, products);
|
|
53
|
+
return product ? total + calculateVolume(p.length, p.width, p.height) * product.quantity : total;
|
|
54
|
+
}, 0);
|
|
55
|
+
const boxLines = boxes
|
|
56
|
+
.map((box) => {
|
|
57
|
+
const cap = calculateVolume(box.width, box.height, box.length);
|
|
58
|
+
const pct = Math.round((totalVolume / cap) * 100);
|
|
59
|
+
return `• ${box.id ?? box.name} (${box.name}): ${cap}cm³ — ${pct}% ${pct > 80 ? "❌ REJEITADA" : "✅ APROVADA"}`;
|
|
60
|
+
})
|
|
61
|
+
.join("\n");
|
|
62
|
+
const productLines = dims.products
|
|
63
|
+
.map((p) => {
|
|
64
|
+
const product = findByName(p.name, products);
|
|
65
|
+
if (!product)
|
|
66
|
+
return `• ${p.name}: não encontrado no carrinho`;
|
|
67
|
+
const vol = calculateVolume(p.length, p.width, p.height);
|
|
68
|
+
return `• ${p.name}: ${product.quantity}x — ${p.length}×${p.width}×${p.height}cm — total ${vol * product.quantity}cm³`;
|
|
69
|
+
})
|
|
70
|
+
.join("\n");
|
|
71
|
+
return (`Especialista em logística: distribua TODOS os produtos nas caixas.\n\n` +
|
|
72
|
+
`REGRA: o volume dos produtos numa caixa não pode passar de 80% da capacidade dela.\n` +
|
|
73
|
+
`Volume total dos produtos: ${totalVolume}cm³\n\n` +
|
|
74
|
+
`CAIXAS:\n${boxLines}\n\nPRODUTOS:\n${productLines}\n\n` +
|
|
75
|
+
`PRIORIDADE ABSOLUTA: menor número de caixas = menor custo. Use 1 caixa quando couber; ` +
|
|
76
|
+
`só use múltiplas se for matematicamente impossível. Use IDs de caixa EXATOS e preserve os nomes e quantidades.\n` +
|
|
77
|
+
`Responda usando a função 'distribute_products'.`);
|
|
78
|
+
}
|
|
79
|
+
export function createOpenAIBoxDistributor(options) {
|
|
80
|
+
const { apiKey, fetch = globalThis.fetch, model = "gpt-4o-mini", temperature = 0.5, apiUrl = "https://api.openai.com/v1/chat/completions", timeoutMs = 20000, } = options;
|
|
81
|
+
if (!apiKey)
|
|
82
|
+
throw new Error("createOpenAIBoxDistributor: apiKey is required");
|
|
83
|
+
if (typeof fetch !== "function")
|
|
84
|
+
throw new Error("createOpenAIBoxDistributor: no fetch available");
|
|
85
|
+
return {
|
|
86
|
+
async distribute(products, boxes, estimatedDimensions) {
|
|
87
|
+
const body = {
|
|
88
|
+
model,
|
|
89
|
+
messages: [
|
|
90
|
+
{ role: "system", content: "Você é um especialista em otimização logística. SEMPRE maximize consolidação e minimize número de caixas." },
|
|
91
|
+
{ role: "user", content: createPrompt(products, boxes, estimatedDimensions) },
|
|
92
|
+
],
|
|
93
|
+
tools: [DISTRIBUTE_TOOL],
|
|
94
|
+
tool_choice: { type: "function", function: { name: "distribute_products" } },
|
|
95
|
+
temperature,
|
|
96
|
+
};
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
99
|
+
let response;
|
|
100
|
+
try {
|
|
101
|
+
response = await fetch(apiUrl, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
104
|
+
body: JSON.stringify(body),
|
|
105
|
+
signal: controller.signal,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
}
|
|
111
|
+
if (!response.ok)
|
|
112
|
+
throw new Error(`OpenAI API Error: ${response.status} ${response.statusText}`);
|
|
113
|
+
const json = (await response.json());
|
|
114
|
+
if (json.error)
|
|
115
|
+
throw new Error(`OpenAI Error: ${json.error.message}`);
|
|
116
|
+
const args = json.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments;
|
|
117
|
+
if (!args)
|
|
118
|
+
throw new Error("IA não conseguiu distribuir os produtos");
|
|
119
|
+
return JSON.parse(args);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meuecommerce/frete-adapter-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Node implementation of @meuecommerce/frete ports (Correios HTTP client) using global fetch. Used by the Shopify/Fly deployment and the MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@meuecommerce/frete": "^0.
|
|
34
|
+
"@meuecommerce/frete": "^0.3.0"
|
|
35
35
|
},
|
|
36
36
|
"module": "./dist/index.js"
|
|
37
37
|
}
|