Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/cost-calculator-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,16 @@ jobs:
$ErrorActionPreference = "Stop"
$site = Join-Path $env:RUNNER_TEMP "cost-calculator-site"
New-Item -ItemType Directory -Path $site | Out-Null
foreach ($file in @("index.html", "styles.css", "app.mjs", "calculator.mjs", "prices.json")) {
foreach ($file in @("index.html", "styles.css", "app.mjs", "calculator.mjs", "prices.json", "favicon.svg")) {
Copy-Item -LiteralPath (Join-Path (Join-Path "tools" "cost_calculator") $file) -Destination $site
}
$index = Join-Path $site "index.html"
$html = Get-Content -LiteralPath $index -Raw
$marker = '<span id="build-info">Local preview (not deployed)</span>'
if (-not $html.Contains($marker)) { throw "Missing deployment commit marker in index.html." }
$commitUrl = "$env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY/commit/$env:GITHUB_SHA"
$commitLink = '<a id="build-info" href="{0}" title="{1}" target="_blank" rel="noopener noreferrer">Commit {2}</a>' -f $commitUrl, $env:GITHUB_SHA, $env:GITHUB_SHA.Substring(0, 7)
$html.Replace($marker, $commitLink) | Set-Content -LiteralPath $index -Encoding utf8 -NoNewline
- uses: actions/upload-pages-artifact@v3
with:
path: ${{ runner.temp }}/cost-calculator-site
Expand Down
8 changes: 6 additions & 2 deletions tools/cost_calculator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,14 @@ Actions tab. Pull requests and other branches cannot deploy.

The repository must have **Settings > Pages > Source > GitHub Actions**
enabled. The deployment publishes exactly `index.html`, `styles.css`,
`app.mjs`, `calculator.mjs`, and `prices.json` at the site's root, excluding
`app.mjs`, `calculator.mjs`, `prices.json`, and the voice-wave `favicon.svg`
at the site's root, excluding
the local server, tests, documentation, and all other repository files.
No dependency installation or build command is needed. All asset URLs are
relative, so the page works under a repository subpath.
During deployment, the workflow stamps the footer with the short commit hash
and a link to that exact source commit. Local previews show
**Local preview (not deployed)** instead.

For this repository, the published address is
**https://microsoft.github.io/voice-agent/** after the first successful
Expand All @@ -61,7 +65,7 @@ is required. Set the app location to this tool's directory, leave the API
location empty, and deploy the existing assets without a build. Configure
authentication and route access rules if the page must not be public.

Deploy only the five browser assets above; no server, credentials, or live
Deploy only the six browser assets above; no server, credentials, or live
Azure connection is needed. Pricing updates require updating the snapshot
and redeploying.

Expand Down
5 changes: 5 additions & 0 deletions tools/cost_calculator/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions tools/cost_calculator/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Voice Agent Cost Lab</title>
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="stylesheet" href="styles.css">
<script type="module" src="app.mjs"></script>
</head>
Expand Down Expand Up @@ -93,13 +94,13 @@ <h2><span>03</span> Call volume</h2>
<h2>Read the estimate, not an invoice</h2>
<p>Each call starts with an uncached prompt. On later turns the prompt and prior conversation are reused at your cache-hit percentage. With equally sized turns, prior caller/agent tokens are each replayed (turns - 1) / 2 times. Cache misses use full input rates.</p>
<p>Native mode reuses audio history; text-response mode reuses caller audio and agent text; cascade and reasoning backends reuse text. Only newly generated speech is synthesized. Reasoning tokens are charged as text output, not retained as visible conversation history.</p>
<p>100% caching is an idealized assumption, not guaranteed prefix-cache eligibility. The supplied 20 input audio tokens/sec is preserved even though the public guide typically estimates ~10 for OpenAI input audio. Actual tokens, silence billing, tools, storage, telephony, hosting capacity, tax, and network charges can change the result and are excluded.</p>
<p>100% caching is an idealized assumption, not guaranteed prefix-cache eligibility. Audio defaults use the public guide's estimates of 10 input and 20 output audio tokens/sec for Azure OpenAI models. Actual tokens, silence billing, tools, storage, telephony, hosting capacity, tax, and network charges can change the result and are excluded.</p>
<p>Model selection is for economic modeling, not a guarantee of South India deployment availability or supported BYOM audio connections. South India Global is the reference for BYOM models and GPT Live. No South India Voice Live managed-tier or BYO speech meters were found, so those use approved Central India references. Azure HD speech output also deliberately uses Central India rates. GPT-6 is explicitly GPT-6 Sol, short-context Standard pricing. GPT Live voice uses the South India Global rate ($3/hour = $0.05/minute); backend model usage is additional and no Azure HD charge is added. GPT Live's delegation frequency and backend context differ in practice; this estimate uses the same turn-based backend assumptions and a session-minute rate.</p>
<p>If a model has no verified managed tier, its economic reference combines AOAI Global model rates with BYO speech rates and is labeled accordingly. It is not a managed Voice Live price or support claim.</p>
<p><a href="https://azure.microsoft.com/en-us/pricing/details/speech/" target="_blank" rel="noreferrer">Azure Speech pricing</a> / <a href="https://azure.microsoft.com/en-us/pricing/details/azure-openai/" target="_blank" rel="noreferrer">Azure OpenAI pricing</a> / <a href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/voice-live" target="_blank" rel="noreferrer">Model tiers &amp; metering</a> / <a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/voice-agent-pricing" target="_blank" rel="noreferrer">Voice Agent billing</a></p>
</section>
</div>
</main>
<footer>VOICE AGENT COST LAB <span>Local calculations. Explicit assumptions. No credentials required.</span></footer>
<footer>VOICE AGENT COST LAB <span>Local calculations. Explicit assumptions. No credentials required.</span><span id="build-info">Local preview (not deployed)</span></footer>
</body>
</html>
4 changes: 2 additions & 2 deletions tools/cost_calculator/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { createServer } from "node:http";
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";

const allowed = new Set(["index.html", "styles.css", "app.mjs", "calculator.mjs", "prices.json"]);
const mime = { html: "text/html", css: "text/css", mjs: "text/javascript", json: "application/json" };
const allowed = new Set(["index.html", "styles.css", "app.mjs", "calculator.mjs", "prices.json", "favicon.svg"]);
const mime = { html: "text/html", css: "text/css", mjs: "text/javascript", json: "application/json", svg: "image/svg+xml" };
const port = Number(process.env.PORT || 8095);
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("PORT must be an integer from 1 to 65535.");
const server = createServer(async (req, res) => {
Expand Down
Loading