feat(call): add streaming voice call experience
Docker Image / Build and Push Docker Image (push) Successful in 2m3s

This commit is contained in:
Codex
2026-07-24 16:36:36 +08:00
parent 5e0361a199
commit 17236bd14e
19 changed files with 1108 additions and 5 deletions
@@ -134,11 +134,26 @@ describe("User", () => {
photo: 40,
},
},
voiceCallBilling: {
enabled: true,
protocolVersion: 2,
chargeMode: "per_turn",
sharesNormalChatFreeDaily: true,
freeTurnsDaily: 30,
costPerPaidTurn: 2,
usageType: "voice_call_turn",
legacyMinuteCostUsed: false,
},
});
expect(entitlements.isVip).toBe(true);
expect(entitlements.creditBalance).toBe(120);
expect(entitlements.historyUnlock.costs.photo).toBe(40);
expect(entitlements.voiceCallBilling).toMatchObject({
protocolVersion: 2,
chargeMode: "per_turn",
costPerPaidTurn: 2,
});
});
it("defaults nullable entitlement sections through schema helpers", () => {
@@ -49,6 +49,17 @@ const HISTORY_UNLOCK_DEFAULTS = {
costs: HISTORY_UNLOCK_COSTS_DEFAULTS,
} as const;
const VOICE_CALL_BILLING_DEFAULTS = {
enabled: false,
protocolVersion: 2,
chargeMode: "per_turn",
sharesNormalChatFreeDaily: true,
freeTurnsDaily: 0,
costPerPaidTurn: 0,
usageType: "voice_call_turn",
legacyMinuteCostUsed: false,
} as const;
export const UserEntitlementsPolicySchema = z
.object({
membershipState: stringOrEmpty,
@@ -105,6 +116,20 @@ export const UserEntitlementsHistoryUnlockSchema = z
.passthrough()
.readonly();
export const VoiceCallBillingSchema = z
.object({
enabled: booleanOrFalse,
protocolVersion: numberOrZero,
chargeMode: stringOrEmpty,
sharesNormalChatFreeDaily: booleanOrFalse,
freeTurnsDaily: numberOrZero,
costPerPaidTurn: numberOrZero,
usageType: stringOrEmpty,
legacyMinuteCostUsed: booleanOrFalse,
})
.passthrough()
.readonly();
export const UserEntitlementsSchema = z
.object({
userId: stringOrEmpty,
@@ -120,6 +145,10 @@ export const UserEntitlementsSchema = z
UserEntitlementsHistoryUnlockSchema,
HISTORY_UNLOCK_DEFAULTS,
),
voiceCallBilling: schemaOr(
VoiceCallBillingSchema,
VOICE_CALL_BILLING_DEFAULTS,
),
})
.readonly();