backend-manager 5.9.4 → 5.9.6
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 +6 -0
- package/package.json +8 -1
- package/src/cli/commands/install.js +4 -3
- package/src/cli/commands/setup-tests/backend-manager.js +2 -1
- package/src/cli/commands/setup-tests/firebase-admin.js +2 -1
- package/src/cli/commands/setup-tests/firebase-functions.js +2 -1
- package/src/cli/utils/safe-install.js +13 -0
- package/src/manager/routes/payments/cancel/post.js +22 -2
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/metadata.json +14 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.html +486 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.md +41 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.mjml +97 -0
- package/{test/email/fixtures/clean.json → src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/structure.json} +16 -4
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/summary.md +1 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/metadata.json +14 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.html +486 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.md +41 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.mjml +97 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/structure.json +42 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/summary.md +1 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/metadata.json +14 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.html +486 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.md +41 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.mjml +97 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/structure.json +42 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/summary.md +1 -0
- package/src/test/fixtures/firebase-project/.temp/test-mode.json +6 -0
- package/src/test/fixtures/firebase-project/database-debug.log +12 -0
- package/src/test/fixtures/firebase-project/firestore-debug.log +136 -0
- package/src/test/fixtures/firebase-project/pubsub-debug.log +17 -0
- package/src/test/test-accounts.js +9 -0
- package/.codeclimate.yml +0 -32
- package/.nvmrc +0 -1
- package/CHANGELOG.md +0 -1432
- package/PROGRESS.md +0 -86
- package/TODO-2.md +0 -121
- package/TODO-CHARGEBLAST.md +0 -32
- package/TODO-WEBHOOK-KEY-LEGACY-REMOVAL.md +0 -15
- package/TODO-WEBHOOK-KEY-UPGRADE.md +0 -138
- package/TODO-email-auth.md +0 -14
- package/TODO.md +0 -187
- package/plans/mcp2.md +0 -247
- package/scripts/test-helper-providers.js +0 -162
- package/scripts/update-disposable-domains.js +0 -44
- package/templates/_.env +0 -42
- package/templates/_.gitignore +0 -60
- package/templates/backend-manager-config.json +0 -249
- package/templates/database.rules.json +0 -83
- package/templates/firebase.json +0 -66
- package/templates/firestore.indexes.json +0 -4
- package/templates/firestore.rules +0 -112
- package/templates/public/404.html +0 -26
- package/templates/public/index.html +0 -24
- package/templates/remoteconfig.template.json +0 -1
- package/templates/storage-lifecycle-config-1-day.json +0 -9
- package/templates/storage-lifecycle-config-30-days.json +0 -9
- package/templates/storage.rules +0 -8
- package/test/_init/accounts-validation.js +0 -58
- package/test/_legacy/ai/index.js +0 -231
- package/test/_legacy/ai/test.jpg +0 -0
- package/test/_legacy/ai/test.pdf +0 -0
- package/test/_legacy/index.js +0 -31
- package/test/_legacy/payment-resolver/chargebee/orders/refunded.json +0 -0
- package/test/_legacy/payment-resolver/chargebee/orders/unpaid.json +0 -98
- package/test/_legacy/payment-resolver/chargebee/subscriptions/active.json +0 -578
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-in-trial.json +0 -38
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-skipped-to-active.json +0 -135
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active-to-cancelled.json +0 -42
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active.json +0 -44
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-cancelled.json +0 -39
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-refund.json +0 -143
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-suspended.json +0 -48
- package/test/_legacy/payment-resolver/coinbase/orders/regular.json +0 -204
- package/test/_legacy/payment-resolver/coinbase/subscriptions/cancelled.json +0 -204
- package/test/_legacy/payment-resolver/coinbase/subscriptions/paid-2.json +0 -125
- package/test/_legacy/payment-resolver/index.js +0 -1558
- package/test/_legacy/payment-resolver/paypal/orders/refunded.json +0 -0
- package/test/_legacy/payment-resolver/paypal/orders/regular.json +0 -120
- package/test/_legacy/payment-resolver/paypal/subscriptions/active-refund-previous-stmnt.json +0 -323
- package/test/_legacy/payment-resolver/paypal/subscriptions/active.json +0 -192
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-in-trial.json +0 -125
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-not-complete.json +0 -76
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue-2.json +0 -114
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue.json +0 -114
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active-to-cancelled.json +0 -111
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active.json +0 -132
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-cancelled.json +0 -107
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-expired.json +0 -91
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-refund.json +0 -147
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-suspended.json +0 -120
- package/test/_legacy/payment-resolver/stripe/orders/refunded.json +0 -0
- package/test/_legacy/payment-resolver/stripe/orders/regular.json +0 -91
- package/test/_legacy/payment-resolver/stripe/subscriptions/active.json +0 -421
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-in-trial.json +0 -349
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active-failed-authorization.json +0 -430
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active.json +0 -319
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-cancelled.json +0 -319
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-refund.json +0 -414
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-suspended.json +0 -319
- package/test/_legacy/payment-resolver/stripe/subscriptions/unsure.json +0 -339
- package/test/_legacy/test-new +0 -1178
- package/test/_legacy/test.md +0 -89
- package/test/_legacy/usage.js +0 -175
- package/test/_legacy/user.js +0 -31
- package/test/ai/tools-live.js +0 -170
- package/test/boot/emulator-boots.js +0 -37
- package/test/content/blog-generate.js +0 -160
- package/test/email/campaign-send.js +0 -45
- package/test/email/consent-lifecycle.js +0 -257
- package/test/email/feedback-and-plain-send.js +0 -52
- package/test/email/fixtures/editorial.json +0 -30
- package/test/email/fixtures/field-report.json +0 -53
- package/test/email/marketing-lifecycle.js +0 -132
- package/test/email/newsletter-generate.js +0 -819
- package/test/email/newsletter-templates.js +0 -491
- package/test/email/templates.js +0 -207
- package/test/email/transactional-send.js +0 -34
- package/test/email/transactional.js +0 -560
- package/test/email/validation.js +0 -710
- package/test/events/auth-delete-race.js +0 -201
- package/test/events/payments/journey-payments-cancel-endpoint.js +0 -100
- package/test/events/payments/journey-payments-cancel.js +0 -182
- package/test/events/payments/journey-payments-discount.js +0 -89
- package/test/events/payments/journey-payments-failure.js +0 -146
- package/test/events/payments/journey-payments-legacy-product.js +0 -149
- package/test/events/payments/journey-payments-one-time-failure.js +0 -111
- package/test/events/payments/journey-payments-one-time.js +0 -136
- package/test/events/payments/journey-payments-plan-change.js +0 -144
- package/test/events/payments/journey-payments-refund-webhook.js +0 -180
- package/test/events/payments/journey-payments-suspend.js +0 -181
- package/test/events/payments/journey-payments-trial-cancel.js +0 -130
- package/test/events/payments/journey-payments-trial.js +0 -171
- package/test/events/payments/journey-payments-uid-resolution.js +0 -132
- package/test/events/payments/journey-payments-upgrade.js +0 -135
- package/test/fixtures/chargebee/invoice-one-time.json +0 -27
- package/test/fixtures/chargebee/subscription-active.json +0 -44
- package/test/fixtures/chargebee/subscription-cancelled.json +0 -42
- package/test/fixtures/chargebee/subscription-in-trial.json +0 -41
- package/test/fixtures/chargebee/subscription-legacy-plan.json +0 -41
- package/test/fixtures/chargebee/subscription-non-renewing.json +0 -41
- package/test/fixtures/chargebee/subscription-paused.json +0 -42
- package/test/fixtures/chargebee/webhook-payment-failed.json +0 -51
- package/test/fixtures/chargebee/webhook-subscription-created.json +0 -47
- package/test/fixtures/paypal/order-approved.json +0 -62
- package/test/fixtures/paypal/order-completed.json +0 -110
- package/test/fixtures/paypal/subscription-active.json +0 -76
- package/test/fixtures/paypal/subscription-cancelled.json +0 -50
- package/test/fixtures/paypal/subscription-suspended.json +0 -65
- package/test/fixtures/stripe/checkout-session-completed.json +0 -130
- package/test/fixtures/stripe/invoice-payment-failed.json +0 -148
- package/test/fixtures/stripe/invoice-subscription-payment-failed.json +0 -28
- package/test/fixtures/stripe/subscription-active.json +0 -161
- package/test/fixtures/stripe/subscription-canceled.json +0 -161
- package/test/fixtures/stripe/subscription-trialing.json +0 -161
- package/test/functions/admin/database-read.js +0 -134
- package/test/functions/admin/database-write.js +0 -159
- package/test/functions/admin/edit-post.js +0 -366
- package/test/functions/admin/firestore-query.js +0 -207
- package/test/functions/admin/firestore-read.js +0 -129
- package/test/functions/admin/firestore-write.js +0 -105
- package/test/functions/admin/get-stats.js +0 -115
- package/test/functions/admin/send-email.js +0 -119
- package/test/functions/admin/send-notification.js +0 -208
- package/test/functions/admin/write-repo-content.js +0 -223
- package/test/functions/general/add-marketing-contact.js +0 -335
- package/test/functions/general/fetch-post.js +0 -54
- package/test/functions/general/generate-uuid.js +0 -114
- package/test/functions/test/authenticate.js +0 -64
- package/test/functions/user/create-custom-token.js +0 -73
- package/test/functions/user/delete.js +0 -135
- package/test/functions/user/get-active-sessions.js +0 -111
- package/test/functions/user/get-subscription-info.js +0 -99
- package/test/functions/user/regenerate-api-keys.js +0 -156
- package/test/functions/user/resolve.js +0 -52
- package/test/functions/user/sign-out-all-sessions.js +0 -94
- package/test/functions/user/sign-up.js +0 -252
- package/test/functions/user/submit-feedback.js +0 -104
- package/test/functions/user/validate-settings.js +0 -82
- package/test/helpers/ai-request-payload.js +0 -300
- package/test/helpers/ai-test-provider.js +0 -202
- package/test/helpers/ai-tools-format.js +0 -383
- package/test/helpers/content/blog-auto-publisher.js +0 -484
- package/test/helpers/content/feed-parser.js +0 -528
- package/test/helpers/content/ghostii-blocks.js +0 -134
- package/test/helpers/content/ghostii-feed-integration.js +0 -404
- package/test/helpers/content/ghostii-write-article.js +0 -243
- package/test/helpers/environment.js +0 -230
- package/test/helpers/infer-contact.js +0 -113
- package/test/helpers/merge-line-files.js +0 -271
- package/test/helpers/payment/chargebee/parse-webhook.js +0 -413
- package/test/helpers/payment/chargebee/to-unified-one-time.js +0 -147
- package/test/helpers/payment/chargebee/to-unified-subscription.js +0 -648
- package/test/helpers/payment/paypal/parse-webhook.js +0 -539
- package/test/helpers/payment/paypal/to-unified-one-time.js +0 -382
- package/test/helpers/payment/paypal/to-unified-subscription.js +0 -820
- package/test/helpers/payment/stripe/parse-webhook.js +0 -447
- package/test/helpers/payment/stripe/to-unified-one-time.js +0 -306
- package/test/helpers/payment/stripe/to-unified-subscription.js +0 -708
- package/test/helpers/sanitize.js +0 -222
- package/test/helpers/slugify.js +0 -394
- package/test/helpers/storage.js +0 -194
- package/test/helpers/user.js +0 -755
- package/test/helpers/webhook-forward.js +0 -418
- package/test/mcp/discovery.js +0 -53
- package/test/mcp/oauth.js +0 -161
- package/test/mcp/protocol.js +0 -268
- package/test/mcp/roles.js +0 -188
- package/test/mcp/utils.js +0 -245
- package/test/routes/admin/create-post.js +0 -364
- package/test/routes/admin/database.js +0 -131
- package/test/routes/admin/deduplicate-image-alts.js +0 -190
- package/test/routes/admin/email.js +0 -114
- package/test/routes/admin/firestore-query.js +0 -204
- package/test/routes/admin/firestore.js +0 -127
- package/test/routes/admin/infer-contact.js +0 -218
- package/test/routes/admin/notification.js +0 -198
- package/test/routes/admin/post-resize-image.js +0 -184
- package/test/routes/admin/post.js +0 -368
- package/test/routes/admin/repo-content.js +0 -223
- package/test/routes/admin/stats.js +0 -112
- package/test/routes/content/post.js +0 -54
- package/test/routes/general/uuid.js +0 -115
- package/test/routes/marketing/campaign.js +0 -125
- package/test/routes/marketing/contact.js +0 -370
- package/test/routes/marketing/email-preferences.js +0 -292
- package/test/routes/marketing/push-send.js +0 -32
- package/test/routes/marketing/webhook-forward.js +0 -61
- package/test/routes/marketing/webhook.js +0 -639
- package/test/routes/payments/cancel.js +0 -140
- package/test/routes/payments/discount.js +0 -80
- package/test/routes/payments/dispute-alert.js +0 -320
- package/test/routes/payments/intent.js +0 -336
- package/test/routes/payments/portal.js +0 -92
- package/test/routes/payments/refund.js +0 -179
- package/test/routes/payments/trial-eligibility.js +0 -71
- package/test/routes/payments/webhook.js +0 -107
- package/test/routes/test/authenticate.js +0 -59
- package/test/routes/test/schema.js +0 -554
- package/test/routes/test/usage.js +0 -342
- package/test/routes/user/api-keys.js +0 -156
- package/test/routes/user/delete.js +0 -136
- package/test/routes/user/feedback.js +0 -104
- package/test/routes/user/sessions.js +0 -199
- package/test/routes/user/settings-validate.js +0 -82
- package/test/routes/user/signup.js +0 -542
- package/test/routes/user/subscription.js +0 -99
- package/test/routes/user/token.js +0 -73
- package/test/routes/user/user.js +0 -157
- package/test/rules/notifications.js +0 -410
- package/test/rules/payments-carts.js +0 -371
- package/test/rules/user.js +0 -396
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test: cross-provider AI tools formatting (libraries/ai)
|
|
3
|
-
*
|
|
4
|
-
* Verifies the unified tools interface added for agentic tool loops:
|
|
5
|
-
* - anthropic-format.js — tool defs, tool choice, unified-message → Claude
|
|
6
|
-
* Messages API mapping (tool_use / tool_result blocks), stop reasons
|
|
7
|
-
* - openai.js formatMessages/normalizeToolEntry/normalizeToolChoice — the
|
|
8
|
-
* direct-messages path and Responses API tool envelopes
|
|
9
|
-
* - ai/index.js normalizeOptions — structured conversations are NOT
|
|
10
|
-
* string-flattened; system injections still apply
|
|
11
|
-
*
|
|
12
|
-
* All pure helpers — no network, no assistant.
|
|
13
|
-
*/
|
|
14
|
-
const format = require('../../src/manager/libraries/ai/providers/anthropic-format.js');
|
|
15
|
-
const OpenAI = require('../../src/manager/libraries/ai/providers/openai.js');
|
|
16
|
-
const AI = require('../../src/manager/libraries/ai/index.js');
|
|
17
|
-
|
|
18
|
-
const { formatMessages, normalizeToolEntry, normalizeToolChoice } = OpenAI._internals;
|
|
19
|
-
const { normalizeOptions, isStructuredMessages, SYSTEM_PROMPT_INJECTIONS } = AI._internals;
|
|
20
|
-
|
|
21
|
-
function noopLog() {}
|
|
22
|
-
|
|
23
|
-
const SAMPLE_TOOL = {
|
|
24
|
-
name: 'check_order',
|
|
25
|
-
description: 'Look up an order',
|
|
26
|
-
parameters: { type: 'object', properties: { orderNumber: { type: 'string' } }, required: ['orderNumber'] },
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
module.exports = {
|
|
30
|
-
description: 'AI cross-provider tools formatting',
|
|
31
|
-
type: 'group',
|
|
32
|
-
tests: [
|
|
33
|
-
// ─── anthropic-format: tool definitions ───
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
name: 'anthropic-tool-defs-map-parameters-to-input-schema',
|
|
37
|
-
async run({ assert }) {
|
|
38
|
-
const defs = format.buildToolDefs([SAMPLE_TOOL]);
|
|
39
|
-
|
|
40
|
-
assert.equal(defs.length, 1, 'one def');
|
|
41
|
-
assert.equal(defs[0].name, 'check_order', 'name preserved');
|
|
42
|
-
assert.equal(defs[0].description, 'Look up an order', 'description preserved');
|
|
43
|
-
assert.deepEqual(defs[0].input_schema, SAMPLE_TOOL.parameters, 'parameters → input_schema');
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
name: 'anthropic-tool-defs-accept-explicit-function-type',
|
|
49
|
-
async run({ assert }) {
|
|
50
|
-
const defs = format.buildToolDefs([{ ...SAMPLE_TOOL, type: 'function' }]);
|
|
51
|
-
|
|
52
|
-
assert.equal(defs[0].name, 'check_order', 'function-typed entry accepted');
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
{
|
|
57
|
-
name: 'anthropic-tool-defs-reject-hosted-tools',
|
|
58
|
-
async run({ assert }) {
|
|
59
|
-
let threw = false;
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
format.buildToolDefs([{ type: 'web_search' }]);
|
|
63
|
-
} catch (e) {
|
|
64
|
-
threw = true;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
assert.equal(threw, true, 'hosted tool types throw on Anthropic');
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
name: 'anthropic-tool-defs-empty-list-returns-empty',
|
|
73
|
-
async run({ assert }) {
|
|
74
|
-
assert.deepEqual(format.buildToolDefs(undefined), [], 'undefined → []');
|
|
75
|
-
assert.deepEqual(format.buildToolDefs([]), [], '[] → []');
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
// ─── anthropic-format: tool choice ───
|
|
80
|
-
|
|
81
|
-
{
|
|
82
|
-
name: 'anthropic-tool-choice-mapping',
|
|
83
|
-
async run({ assert }) {
|
|
84
|
-
assert.deepEqual(format.buildToolChoice('auto'), { type: 'auto' }, 'auto');
|
|
85
|
-
assert.deepEqual(format.buildToolChoice('required'), { type: 'any' }, 'required → any');
|
|
86
|
-
assert.deepEqual(format.buildToolChoice('none'), { type: 'none' }, 'none');
|
|
87
|
-
assert.deepEqual(format.buildToolChoice({ name: 'check_order' }), { type: 'tool', name: 'check_order' }, 'specific tool');
|
|
88
|
-
assert.equal(format.buildToolChoice(undefined), undefined, 'undefined passes through');
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
// ─── anthropic-format: message building ───
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
name: 'anthropic-messages-assistant-toolcalls-become-tool-use-blocks',
|
|
96
|
-
async run({ assert }) {
|
|
97
|
-
const { system, messages } = format.buildMessages({
|
|
98
|
-
messages: [
|
|
99
|
-
{ role: 'system', content: 'sys' },
|
|
100
|
-
{ role: 'user', content: 'check my order 123' },
|
|
101
|
-
{ role: 'assistant', content: 'Let me check.', toolCalls: [{ id: 'call_1', name: 'check_order', arguments: { orderNumber: '123' } }] },
|
|
102
|
-
],
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
assert.equal(system, 'sys', 'system extracted');
|
|
106
|
-
assert.equal(messages.length, 2, 'user + assistant');
|
|
107
|
-
|
|
108
|
-
const assistantTurn = messages[1];
|
|
109
|
-
assert.equal(assistantTurn.role, 'assistant', 'assistant role');
|
|
110
|
-
assert.equal(assistantTurn.content[0].type, 'text', 'text block first');
|
|
111
|
-
assert.equal(assistantTurn.content[0].text, 'Let me check.', 'text content');
|
|
112
|
-
assert.equal(assistantTurn.content[1].type, 'tool_use', 'tool_use block');
|
|
113
|
-
assert.equal(assistantTurn.content[1].id, 'call_1', 'call id');
|
|
114
|
-
assert.deepEqual(assistantTurn.content[1].input, { orderNumber: '123' }, 'arguments → input');
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
{
|
|
119
|
-
name: 'anthropic-messages-consecutive-tool-results-merge-into-one-user-turn',
|
|
120
|
-
async run({ assert }) {
|
|
121
|
-
const { messages } = format.buildMessages({
|
|
122
|
-
messages: [
|
|
123
|
-
{ role: 'user', content: 'hi' },
|
|
124
|
-
{ role: 'assistant', toolCalls: [
|
|
125
|
-
{ id: 'call_1', name: 'a', arguments: {} },
|
|
126
|
-
{ id: 'call_2', name: 'b', arguments: {} },
|
|
127
|
-
] },
|
|
128
|
-
{ role: 'tool', toolCallId: 'call_1', content: 'result A' },
|
|
129
|
-
{ role: 'tool', toolCallId: 'call_2', content: 'result B' },
|
|
130
|
-
],
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
assert.equal(messages.length, 3, 'user + assistant + ONE merged tool-result user turn');
|
|
134
|
-
|
|
135
|
-
const resultTurn = messages[2];
|
|
136
|
-
assert.equal(resultTurn.role, 'user', 'tool results ride a user turn');
|
|
137
|
-
assert.equal(resultTurn.content.length, 2, 'both results in one turn');
|
|
138
|
-
assert.equal(resultTurn.content[0].type, 'tool_result', 'tool_result block');
|
|
139
|
-
assert.equal(resultTurn.content[0].tool_use_id, 'call_1', 'first result id');
|
|
140
|
-
assert.equal(resultTurn.content[1].tool_use_id, 'call_2', 'second result id');
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
{
|
|
145
|
-
name: 'anthropic-messages-developer-role-folds-into-system',
|
|
146
|
-
async run({ assert }) {
|
|
147
|
-
const { system, messages } = format.buildMessages({
|
|
148
|
-
messages: [
|
|
149
|
-
{ role: 'system', content: 'platform' },
|
|
150
|
-
{ role: 'developer', content: 'operator' },
|
|
151
|
-
{ role: 'user', content: 'hi' },
|
|
152
|
-
],
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
assert.equal(system, 'platform\n\noperator', 'developer folded into system');
|
|
156
|
-
assert.equal(messages.length, 1, 'only the user turn remains');
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
{
|
|
161
|
-
name: 'anthropic-messages-raw-block-arrays-pass-through',
|
|
162
|
-
async run({ assert }) {
|
|
163
|
-
const rawBlocks = [{ type: 'text', text: 'hello' }, { type: 'tool_use', id: 'x', name: 't', input: {} }];
|
|
164
|
-
const { messages } = format.buildMessages({
|
|
165
|
-
messages: [
|
|
166
|
-
{ role: 'user', content: 'hi' },
|
|
167
|
-
{ role: 'assistant', content: rawBlocks },
|
|
168
|
-
],
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
assert.deepEqual(messages[1].content, rawBlocks, 'raw blocks untouched');
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
|
|
175
|
-
{
|
|
176
|
-
name: 'anthropic-messages-legacy-prompt-message-form-unchanged',
|
|
177
|
-
async run({ assert }) {
|
|
178
|
-
const { system, messages } = format.buildMessages({
|
|
179
|
-
prompt: { content: 'sys prompt' },
|
|
180
|
-
message: { content: 'user msg' },
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
assert.equal(system, 'sys prompt', 'system from prompt');
|
|
184
|
-
assert.deepEqual(messages, [{ role: 'user', content: 'user msg' }], 'single user turn');
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
|
|
188
|
-
// ─── anthropic-format: response extraction ───
|
|
189
|
-
|
|
190
|
-
{
|
|
191
|
-
name: 'anthropic-extract-tool-calls-and-stop-reasons',
|
|
192
|
-
async run({ assert }) {
|
|
193
|
-
const calls = format.extractToolCalls([
|
|
194
|
-
{ type: 'text', text: 'thinking...' },
|
|
195
|
-
{ type: 'tool_use', id: 'call_9', name: 'check_order', input: { orderNumber: '9' } },
|
|
196
|
-
]);
|
|
197
|
-
|
|
198
|
-
assert.equal(calls.length, 1, 'one call');
|
|
199
|
-
assert.deepEqual(calls[0], { id: 'call_9', name: 'check_order', arguments: { orderNumber: '9' } }, 'normalized shape');
|
|
200
|
-
|
|
201
|
-
assert.equal(format.mapStopReason('tool_use'), 'tool_use', 'tool_use');
|
|
202
|
-
assert.equal(format.mapStopReason('max_tokens'), 'max_tokens', 'max_tokens');
|
|
203
|
-
assert.equal(format.mapStopReason('end_turn'), 'end', 'end_turn → end');
|
|
204
|
-
assert.equal(format.mapStopReason('stop_sequence'), 'end', 'stop_sequence → end');
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
// ─── openai: direct-messages path ───
|
|
209
|
-
|
|
210
|
-
{
|
|
211
|
-
name: 'openai-format-messages-maps-toolcalls-to-function-call-items',
|
|
212
|
-
async run({ assert }) {
|
|
213
|
-
const input = formatMessages([
|
|
214
|
-
{ role: 'system', content: 'sys' },
|
|
215
|
-
{ role: 'user', content: 'check order 123' },
|
|
216
|
-
{ role: 'assistant', content: 'Checking.', toolCalls: [{ id: 'call_1', name: 'check_order', arguments: { orderNumber: '123' } }] },
|
|
217
|
-
{ role: 'tool', toolCallId: 'call_1', content: '{"status":"shipped"}' },
|
|
218
|
-
], noopLog);
|
|
219
|
-
|
|
220
|
-
assert.equal(input.length, 5, 'system + user + assistant text + function_call + function_call_output');
|
|
221
|
-
assert.equal(input[0].role, 'system', 'system turn');
|
|
222
|
-
assert.equal(input[1].role, 'user', 'user turn');
|
|
223
|
-
assert.equal(input[2].role, 'assistant', 'assistant text turn');
|
|
224
|
-
assert.equal(input[2].content[0].type, 'output_text', 'assistant uses output_text');
|
|
225
|
-
|
|
226
|
-
const callItem = input[3];
|
|
227
|
-
assert.equal(callItem.type, 'function_call', 'function_call item');
|
|
228
|
-
assert.equal(callItem.call_id, 'call_1', 'call id');
|
|
229
|
-
assert.equal(callItem.name, 'check_order', 'name');
|
|
230
|
-
assert.equal(callItem.arguments, JSON.stringify({ orderNumber: '123' }), 'arguments stringified');
|
|
231
|
-
|
|
232
|
-
const outputItem = input[4];
|
|
233
|
-
assert.equal(outputItem.type, 'function_call_output', 'function_call_output item');
|
|
234
|
-
assert.equal(outputItem.call_id, 'call_1', 'output call id');
|
|
235
|
-
assert.equal(outputItem.output, '{"status":"shipped"}', 'output string');
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
|
|
239
|
-
{
|
|
240
|
-
name: 'openai-format-messages-toolcall-turn-without-text-emits-no-message-item',
|
|
241
|
-
async run({ assert }) {
|
|
242
|
-
const input = formatMessages([
|
|
243
|
-
{ role: 'user', content: 'hi' },
|
|
244
|
-
{ role: 'assistant', toolCalls: [{ id: 'c1', name: 't', arguments: {} }] },
|
|
245
|
-
], noopLog);
|
|
246
|
-
|
|
247
|
-
assert.equal(input.length, 2, 'user + function_call only');
|
|
248
|
-
assert.equal(input[1].type, 'function_call', 'no empty assistant message item');
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
|
|
252
|
-
// ─── openai: tool envelopes ───
|
|
253
|
-
|
|
254
|
-
{
|
|
255
|
-
name: 'openai-tool-entry-normalization',
|
|
256
|
-
async run({ assert }) {
|
|
257
|
-
const normalized = normalizeToolEntry(SAMPLE_TOOL);
|
|
258
|
-
|
|
259
|
-
assert.equal(normalized.type, 'function', 'function envelope added');
|
|
260
|
-
assert.equal(normalized.name, 'check_order', 'name preserved');
|
|
261
|
-
assert.deepEqual(normalized.parameters, SAMPLE_TOOL.parameters, 'parameters preserved');
|
|
262
|
-
|
|
263
|
-
const hosted = normalizeToolEntry({ type: 'web_search' });
|
|
264
|
-
assert.deepEqual(hosted, { type: 'web_search' }, 'hosted tools pass verbatim');
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
{
|
|
269
|
-
name: 'openai-tool-choice-normalization',
|
|
270
|
-
async run({ assert }) {
|
|
271
|
-
assert.equal(normalizeToolChoice('auto'), 'auto', 'auto passes');
|
|
272
|
-
assert.equal(normalizeToolChoice('required'), 'required', 'required passes');
|
|
273
|
-
assert.equal(normalizeToolChoice('none'), 'none', 'none passes');
|
|
274
|
-
assert.deepEqual(normalizeToolChoice({ name: 'check_order' }), { type: 'function', name: 'check_order' }, 'specific tool');
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
|
|
278
|
-
// ─── index.js: normalizeOptions on structured conversations ───
|
|
279
|
-
|
|
280
|
-
{
|
|
281
|
-
name: 'structured-detection',
|
|
282
|
-
async run({ assert }) {
|
|
283
|
-
assert.equal(isStructuredMessages([{ role: 'user', content: 'hi' }]), false, 'plain text is not structured');
|
|
284
|
-
assert.equal(isStructuredMessages([{ role: 'tool', toolCallId: 'x', content: 'r' }]), true, 'tool turn is structured');
|
|
285
|
-
assert.equal(isStructuredMessages([{ role: 'assistant', toolCalls: [{ id: 'x', name: 't' }] }]), true, 'toolCalls turn is structured');
|
|
286
|
-
assert.equal(isStructuredMessages([{ role: 'assistant', content: [{ type: 'tool_use', id: 'x' }] }]), true, 'raw tool_use blocks are structured');
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
|
|
290
|
-
{
|
|
291
|
-
name: 'normalize-options-structured-keeps-turns-and-injects-system-rules',
|
|
292
|
-
async run({ assert }) {
|
|
293
|
-
const messages = [
|
|
294
|
-
{ role: 'system', content: 'agent instructions' },
|
|
295
|
-
{ role: 'user', content: 'check order 1' },
|
|
296
|
-
{ role: 'assistant', toolCalls: [{ id: 'c1', name: 'check_order', arguments: {} }] },
|
|
297
|
-
{ role: 'tool', toolCallId: 'c1', content: 'shipped' },
|
|
298
|
-
];
|
|
299
|
-
const out = normalizeOptions({ messages });
|
|
300
|
-
|
|
301
|
-
assert.equal(out.messages.length, 4, 'no turns added or dropped');
|
|
302
|
-
assert.equal(
|
|
303
|
-
out.messages[0].content.includes(SYSTEM_PROMPT_INJECTIONS[0]),
|
|
304
|
-
true,
|
|
305
|
-
'system rules injected into the system turn',
|
|
306
|
-
);
|
|
307
|
-
assert.equal(
|
|
308
|
-
out.messages[0].content.includes('agent instructions'),
|
|
309
|
-
true,
|
|
310
|
-
'original system content preserved',
|
|
311
|
-
);
|
|
312
|
-
assert.deepEqual(out.messages[2], messages[2], 'toolCalls turn untouched');
|
|
313
|
-
assert.deepEqual(out.messages[3], messages[3], 'tool result turn untouched');
|
|
314
|
-
assert.equal(out.prompt, undefined, 'prompt NOT synthesized in structured mode');
|
|
315
|
-
},
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
{
|
|
319
|
-
name: 'normalize-options-structured-without-system-prepends-rules-turn',
|
|
320
|
-
async run({ assert }) {
|
|
321
|
-
const out = normalizeOptions({
|
|
322
|
-
messages: [
|
|
323
|
-
{ role: 'user', content: 'hi' },
|
|
324
|
-
{ role: 'tool', toolCallId: 'c1', content: 'r' },
|
|
325
|
-
],
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
assert.equal(out.messages.length, 3, 'rules turn prepended');
|
|
329
|
-
assert.equal(out.messages[0].role, 'system', 'first turn is system');
|
|
330
|
-
assert.equal(out.messages[0].content.includes(SYSTEM_PROMPT_INJECTIONS[1]), true, 'rules content');
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
{
|
|
335
|
-
name: 'normalize-options-legacy-plain-messages-unchanged-behavior',
|
|
336
|
-
async run({ assert }) {
|
|
337
|
-
const out = normalizeOptions({
|
|
338
|
-
messages: [
|
|
339
|
-
{ role: 'system', content: 'sys' },
|
|
340
|
-
{ role: 'user', content: 'hello' },
|
|
341
|
-
],
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
assert.equal(typeof out.prompt?.content, 'string', 'prompt synthesized for legacy mode');
|
|
345
|
-
assert.equal(out.prompt.content.includes('sys'), true, 'prompt carries system content');
|
|
346
|
-
assert.equal(out.message?.content, 'hello', 'message carries last user turn');
|
|
347
|
-
},
|
|
348
|
-
},
|
|
349
|
-
|
|
350
|
-
{
|
|
351
|
-
name: 'normalize-options-structured-system-content-as-array-injects-rules',
|
|
352
|
-
async run({ assert }) {
|
|
353
|
-
const messages = [
|
|
354
|
-
{
|
|
355
|
-
role: 'system',
|
|
356
|
-
content: [
|
|
357
|
-
{ type: 'text', text: 'existing instruction' },
|
|
358
|
-
{ type: 'image', source: { type: 'url', url: 'https://example.com/img.png' } },
|
|
359
|
-
],
|
|
360
|
-
},
|
|
361
|
-
{ role: 'user', content: 'check order 1' },
|
|
362
|
-
{ role: 'assistant', toolCalls: [{ id: 'c1', name: 'check_order', arguments: {} }] },
|
|
363
|
-
{ role: 'tool', toolCallId: 'c1', content: 'shipped' },
|
|
364
|
-
];
|
|
365
|
-
const out = normalizeOptions({ messages });
|
|
366
|
-
|
|
367
|
-
assert.equal(out.messages.length, 4, 'no turns added or dropped');
|
|
368
|
-
assert.equal(Array.isArray(out.messages[0].content), true, 'system content stays as array');
|
|
369
|
-
assert.equal(out.messages[0].content[0].type, 'text', 'rules prepended as text block');
|
|
370
|
-
assert.equal(
|
|
371
|
-
out.messages[0].content[0].text.includes(SYSTEM_PROMPT_INJECTIONS[0]),
|
|
372
|
-
true,
|
|
373
|
-
'system rules injected into prepended text block',
|
|
374
|
-
);
|
|
375
|
-
assert.equal(out.messages[0].content[1].type, 'text', 'original text block preserved');
|
|
376
|
-
assert.equal(out.messages[0].content[1].text, 'existing instruction', 'original text content intact');
|
|
377
|
-
assert.equal(out.messages[0].content[2].type, 'image', 'original image block preserved');
|
|
378
|
-
assert.deepEqual(out.messages[2], messages[2], 'toolCalls turn untouched');
|
|
379
|
-
assert.deepEqual(out.messages[3], messages[3], 'tool result turn untouched');
|
|
380
|
-
},
|
|
381
|
-
},
|
|
382
|
-
],
|
|
383
|
-
};
|